Skip to content

feat(server): add OpenAPI spec generation for RESTful API#2498

Merged
ymc9 merged 16 commits intodevfrom
feat/server-openapi-spec
Mar 20, 2026
Merged

feat(server): add OpenAPI spec generation for RESTful API#2498
ymc9 merged 16 commits intodevfrom
feat/server-openapi-spec

Conversation

@ymc9
Copy link
Member

@ymc9 ymc9 commented Mar 19, 2026

Summary

  • Extract OpenAPI spec generation from REST and RPC handlers into dedicated RestApiSpecGenerator and RPCApiSpecGenerator classes
  • Add shared spec-utils.ts with helpers for model/operation/field/filter-kind inclusion checks
  • Support queryOptions.slicing.fields with includedFilterKinds/excludedFilterKinds to control which filter operators appear in generated OpenAPI specs
    • REST: Controls individual filter[field][op] query parameters per filter kind (Equality, Like, Range, List)
    • RPC: Controls per-operator properties (equals, contains, lt, etc.) in WhereInput schemas, with oneOf shorthand when Equality is included
  • Add queryOptions support to handler constructors (with Zod validation) for omit and slicing configuration
  • Migrate OpenAPI v3.1 tests and baselines from zenstack-v2
  • 76 OpenAPI tests passing (REST + RPC + migrated)

Test plan

  • All 76 OpenAPI tests pass (npx vitest run packages/server/test/openapi/)
  • Verify REST filter params respect excludedFilterKinds / includedFilterKinds
  • Verify RPC WhereInput schemas reflect per-operator filter kind control
  • Verify $all field-level slicing applies to all fields with field-specific overrides
  • Verify migrated v3.1 baseline tests still pass

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added OpenAPI 3.1.0 specification generation for both REST and RPC APIs.
    • Introduced query options to customize generated specifications (model/operation slicing, field omission, filter kind control).
    • Added support for custom titles, versions, descriptions, and summaries in generated specs.
  • Tests

    • Added comprehensive test suites for REST and RPC OpenAPI generation.
    • Added baseline YAML fixtures for specification validation.
  • Chores

    • Added openapi-types dependency.

…pport

Refactor REST and RPC API handlers to extract OpenAPI spec generation into
dedicated classes (RestApiSpecGenerator, RPCApiSpecGenerator). Add support
for queryOptions.slicing.fields with includedFilterKinds/excludedFilterKinds
to control which filter operators appear in generated specs. REST generator
controls individual filter query parameters; RPC generator controls per-operator
properties in WhereInput schemas.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 19, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This pull request introduces OpenAPI 3.1 specification generation for ZenStack's REST and RPC API handlers. It adds filtering utilities, type definitions, validation schemas, two new spec generator implementations, handler integrations, and comprehensive test coverage with baseline fixtures.

Changes

Cohort / File(s) Summary
Package Dependencies
packages/server/package.json
Added openapi-types v12.1.3 dependency.
Common Utilities & Types
packages/server/src/api/common/spec-utils.ts, packages/server/src/api/common/types.ts, packages/server/src/api/common/schemas.ts
Introduced filtering predicates for models, operations, procedures, and fields based on query options; defined CommonHandlerOptions, OpenApiSpecOptions, and OpenApiSpecGenerator types; added Zod validation schemas for query option structure including omit and slicing rules.
API Handler Integration
packages/server/src/api/index.ts, packages/server/src/api/rest/index.ts, packages/server/src/api/rpc/index.ts
Extended RestApiHandler and RPCApiHandler to implement OpenApiSpecGenerator with public generateSpec() methods; widened handler options to include CommonHandlerOptions; re-exported spec generator types.
OpenAPI Spec Generators
packages/server/src/api/rest/openapi.ts
Implemented RestApiSpecGenerator producing JSON:API-style OpenAPI 3.1 documents with model resource endpoints, relationship routes, procedure routes, and comprehensive schema generation for requests/responses.
OpenAPI Spec Generators
packages/server/src/api/rpc/openapi.ts
Implemented RPCApiSpecGenerator producing RPC-style OpenAPI 3.1 documents with per-model CRUD operation routes, transaction endpoint, procedure routes, and extensive per-model argument/output schemas with filter operator support.
REST API Tests
packages/server/test/openapi/rest-openapi.test.ts, packages/server/test/openapi/migrated/rest-migrated.test.ts
Added comprehensive Vitest suites validating REST spec generation including paths, methods, schemas, model mapping, compound IDs, type coverage, query option slicing/omission behaviors, metadata descriptions, and enum schemas.
RPC API Tests
packages/server/test/openapi/rpc-openapi.test.ts, packages/server/test/openapi/migrated/rpc-migrated.test.ts
Added comprehensive Vitest suites validating RPC spec generation including operation paths, transaction endpoints, schema presence, type mappings, query option behaviors, filter operator slicing, metadata handling, and procedure routes.
Test Fixtures
packages/server/test/openapi/migrated/baseline/rest-type-coverage-3.1.0.baseline.yaml
Added OpenAPI 3.1.0 baseline YAML defining JSON:API-style endpoints, schemas, and parameters for REST API type coverage testing.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Poem

🐰 Spec generators hop and bound,
REST and RPC paths now abound,
OpenAPI docs with schemas so bright,
Query slicing keeps models tight,
Tests verify every endpoint's right!

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Title check ✅ Passed The title 'feat(server): add OpenAPI spec generation for RESTful API' is specific and clearly describes the main change—adding OpenAPI spec generation capability to the server's REST API.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/server-openapi-spec
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@claude
Copy link
Contributor

claude bot commented Mar 19, 2026

Claude finished @ymc9's task —— View job


Code Review for OpenAPI Spec Generation PR

I've conducted a comprehensive review of this PR that adds OpenAPI spec generation with filter kind slicing support. Here's my detailed feedback:

Review Tasks:

  • Analyze architectural changes and new class structure
  • Review filter kind slicing implementation
  • Examine test coverage and quality
  • Check for potential bugs and edge cases
  • Review security considerations
  • Assess performance implications
  • Verify migration quality from zenstack-v2

Strengths

Excellent Architecture

  • Clean separation of concerns: Extracting OpenAPI spec generation into dedicated RestApiSpecGenerator and RPCApiSpecGenerator classes is a solid architectural decision that improves maintainability
  • Shared utilities: The spec-utils.ts module provides reusable functions for inclusion/exclusion logic across REST and RPC handlers
  • Interface-based design: OpenApiSpecGenerator interface enables extensibility and testing

Robust Filter Kind Slicing Implementation

  • Well-designed inclusion/exclusion logic: isFilterKindIncluded function properly handles both includedFilterKinds and excludedFilterKinds with correct precedence (exclusion takes priority)
  • Proper fallback hierarchy: Model-specific → $all model → global defaults works as expected
  • Comprehensive filter kind coverage: Supports all four filter kinds (Equality, Like, Range, List) with appropriate type constraints

Strong Test Coverage

  • Comprehensive testing: 76 OpenAPI tests covering both REST and RPC approaches
  • Migration validation: Proper migration from zenstack-v2 with baseline comparisons
  • Edge case testing: Tests for filter kind exclusions, field-level slicing, and type coverage

⚠️ Areas for Improvement

Security Considerations

  1. Insufficient queryOptions validation:

    // Current validation is too permissive
    queryOptions: z.object().optional()

    Recommendation: Add stricter Zod schema validation for slicing, omit, and their nested properties to prevent malformed configurations.

  2. Type safety gaps: Filter kind strings ('Equality', 'Like', etc.) are magic strings that could benefit from enum/const assertions:

    // Suggest adding:
    export const FILTER_KINDS = ['Equality', 'Like', 'Range', 'List'] as const;
    export type FilterKind = typeof FILTER_KINDS[number];

Performance Considerations

  1. Repeated schema lookups: In packages/server/src/api/rest/openapi.ts:77-98, the code performs multiple model lookups that could be cached
  2. String operations in loops: lowerCaseFirst(modelName) is called repeatedly in filter generation loops - consider memoization

Potential Edge Cases

  1. Empty filter configurations: No validation for empty includedFilterKinds arrays, which could lead to unexpected behavior
  2. Circular model references: Spec generation might not handle deeply nested self-referential models gracefully
  3. Field name collisions: No validation for field names that might conflict with OpenAPI reserved words

Code Quality Issues

  1. Magic numbers: Line 408 in rest/openapi.ts hardcodes 'id' field - should use schema introspection
  2. Error handling gaps: No specific error handling for malformed queryOptions.slicing configurations
  3. Documentation: Missing JSDoc for complex filter kind logic in spec generators

🔧 Specific Recommendations

packages/server/src/api/common/spec-utils.ts

  • Line 88: Consider adding validation that $all doesn't conflict with specific field names
  • Lines 91-95: Add input sanitization for filter kind strings

packages/server/src/api/rest/openapi.ts

  • Lines 424-458: Extract filter parameter generation into separate methods for better readability
  • Line 408: Replace hardcoded 'id' with dynamic ID field detection

packages/server/src/api/rpc/openapi.ts

  • Lines 697-737: The oneOf shorthand logic could be more explicit about when it applies

📋 Migration Quality

The migration from zenstack-v2 appears thorough:

  • ✅ Baseline files properly copied and validated
  • ✅ Test structure maintains consistency
  • ✅ OpenAPI v3.1 compliance maintained

🎯 Overall Assessment

This is a high-quality PR that significantly improves the codebase architecture while adding valuable functionality. The filter kind slicing feature is well-implemented and the testing is comprehensive.

Recommended Actions:

  1. Add stricter queryOptions validation schema
  2. Implement filter kind constants/types for type safety
  3. Add performance optimizations for repeated lookups
  4. Enhance error handling for edge cases

The code is ready for merge with minor improvements to address the security and performance considerations mentioned above.

@ymc9 ymc9 changed the title feat(server): add OpenAPI spec generation with filter kind slicing feat(server): add OpenAPI spec generation Mar 19, 2026
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 14

🧹 Nitpick comments (4)
packages/server/test/openapi/migrated/rest-migrated.test.ts (1)

198-208: Consider using it.skip() or test.skipIf() for clearer test reporting.

When baseline files are missing, the test returns early with a console.warn. This passes the test silently rather than marking it as skipped in test reports. Using Vitest's skip mechanism would provide better visibility.

♻️ Alternative using skipIf
it.skipIf(!fs.existsSync(path.join(__dirname, 'baseline', 'rest-3.1.0.baseline.yaml')))(
    'baseline comparison - rest-3.1.0',
    async () => {
        const baselinePath = path.join(__dirname, 'baseline', 'rest-3.1.0.baseline.yaml');
        const baseline = YAML.parse(fs.readFileSync(baselinePath, 'utf-8'));
        expect(baseline.openapi).toBe('3.1.0');
        expect(baseline.components?.schemas).toBeDefined();
        expect(baseline.paths).toBeDefined();
    }
);

Alternatively, keep the current approach if you prefer tests to pass when baselines are intentionally absent during development.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/server/test/openapi/migrated/rest-migrated.test.ts` around lines 198
- 208, The test "baseline comparison - rest-3.1.0" currently uses an early
return after checking fs.existsSync(path.join(...)) which silently passes when
the baseline is missing; change this to use Vitest's skip mechanism (e.g.,
it.skip or it.skipIf) so the test is explicitly marked skipped instead of
returning early—remove the console.warn and early return, and wrap or declare
the test using it.skipIf(!fs.existsSync(path.join(__dirname, 'baseline',
'rest-3.1.0.baseline.yaml'))) while keeping the body that reads
YAML.parse(fs.readFileSync(...)) and the existing expects intact.
packages/server/src/api/index.ts (1)

1-3: Consider exporting the concrete spec generator classes.

The RestApiSpecGenerator and RPCApiSpecGenerator classes are exported from ./rest and ./rpc respectively (per context snippets), but not re-exported from this main barrel module. If external consumers should have direct access to these generators (independent of the handlers), consider adding them here for API surface consistency.

🔧 Optional: Add generator class exports
 export { RestApiHandler, type RestApiHandlerOptions } from './rest';
 export { RPCApiHandler, type RPCApiHandlerOptions } from './rpc';
 export type { OpenApiSpecGenerator, OpenApiSpecOptions } from './common/types';
+export { RestApiSpecGenerator } from './rest';
+export { RPCApiSpecGenerator } from './rpc';
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/server/src/api/index.ts` around lines 1 - 3, Add re-exports for the
concrete spec generator classes so external consumers can import them from the
main barrel; specifically export RestApiSpecGenerator and RPCApiSpecGenerator
from the module by adding "export { RestApiSpecGenerator } from './rest';" and
"export { RPCApiSpecGenerator } from './rpc';" alongside the existing handler
and type exports (keeping the existing RestApiHandler, RPCApiHandler, and
OpenApiSpecGenerator/OpenApiSpecOptions exports).
packages/server/test/openapi/migrated/rpc-migrated.test.ts (2)

165-197: Same recommendation: consider using it.skip() for missing baselines.

Consistent with the REST test file feedback, using Vitest's skip mechanism would provide clearer test reporting when baseline files are absent.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/server/test/openapi/migrated/rpc-migrated.test.ts` around lines 165
- 197, The three tests ('baseline comparison - rpc-3.1.0', 'baseline comparison
- rpc-3.1.0-omit', 'baseline comparison - rpc-type-coverage-3.1.0') currently
early-return with console.warn when baseline files are missing; replace that
pattern with Vitest's skip so missing baselines show as skipped tests. For each
test, compute baselinePath and then do: if (!fs.existsSync(baselinePath)) {
it.skip('<same test title>', () => {}); return; } or better yet choose the test
function dynamically (const maybeIt = fs.existsSync(baselinePath) ? it :
it.skip) and call maybeIt('<same test title>', async () => { const baseline =
YAML.parse(...); expect(...); }); ensuring the test name and assertions
(YAML.parse and expect lines) remain unchanged.

128-129: Remove unnecessary as any type assertions.

The OpenAPIV3_1.InfoObject type from openapi-types includes both description and summary as optional fields (per OpenAPI 3.1 spec). The as any casts on lines 128-129 are unnecessary and can be removed:

expect(spec.info.description).toBe('A custom API');
expect(spec.info.summary).toBe('awesome api');
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/server/test/openapi/migrated/rpc-migrated.test.ts` around lines 128
- 129, Remove the unnecessary `as any` casts on the OpenAPI info assertions:
replace `expect((spec.info as any).description).toBe('A custom API')` and
`expect((spec.info as any).summary).toBe('awesome api')` with
`expect(spec.info.description).toBe('A custom API')` and
`expect(spec.info.summary).toBe('awesome api')`; ensure `spec` is typed as an
OpenAPIV3_1.Document (or otherwise that `spec.info` is recognized) so the
`description` and `summary` properties on `spec.info` are accepted by TypeScript
without casting.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/server/src/api/common/spec-utils.ts`:
- Around line 24-39: The isOperationIncluded function treats a model-specific
slicing block as a full replacement of $all (using (slicing.models[modelKey] ??
slicing.models.$all)), which drops global rules; instead merge the $all block
with the model-specific block so each property (excludedOperations,
includedOperations) considers defaults from $all and overrides from
models[modelKey] before evaluating includes/excludes. Update isOperationIncluded
to compute a combined modelSlicing = { ...slicing.models.$all,
...slicing.models[modelKey] } (or equivalent property-level merge) and then
check modelSlicing.excludedOperations and modelSlicing.includedOperations; apply
the same merge-fix to the analogous logic later in the file (the field-slicing
check around lines 75-97).

In `@packages/server/src/api/rest/handler.ts`:
- Around line 1267-1298: processUpdate is manually constructing relation
payloads (set/connect) causing numeric IDs to remain strings and compound IDs to
be formatted differently than processCreate/processRelationshipCRUD; replace the
manual constructions with calls to makeIdConnect so relation payloads are
normalized. Concretely, inside the relationships loop where
updatePayload.data[key] is set for collection and non-collection branches, call
makeIdConnect(relationInfo.idFields, item.id) for each item (for collections use
set: enumerate(...).map(idObj => ({ [makeDefaultIdKey?]?: idObj }) if
makeIdConnect returns the nested unique object adjust accordingly) and for
single relations use connect: makeIdConnect(relationInfo.idFields,
data.data.id); keep updatePayload.include assignment but ensure IDs produced
match the shape used by processCreate/processRelationshipCRUD. Use the existing
symbols processUpdate, makeIdConnect, makeDefaultIdKey, processCreate,
processRelationshipCRUD, relationInfo, updatePayload and relationships to locate
and update the code.
- Around line 1817-1903: The bug is that item, curr and currType are created
once per query key and reused across enumerate(value) entries causing repeated
filter params (e.g. ?filter[id]=1&filter[id]=2) to overwrite the same object;
fix it by moving the creation/initialization of item, curr and currType inside
the for (const filterValue of enumerate(value)) loop so each filterValue builds
a fresh item object, reset curr = item and currType = typeInfo at the start of
each iteration, and then continue using existing logic (getIdFields,
getModelInfo, makeFilterValue, and pushing into items) so each value produces
its own independent filter object.
- Around line 510-512: handleGenericError currently appends err.stack into the
response body which leaks internals; change handleGenericError to only return a
generic error message via makeError('unknownError', err instanceof Error ?
err.message : 'Unknown error') and ensure the full error and stack are logged
internally (using the existing logger on the class or console.error) instead of
exposing the stack in the HTTP response; keep references to the
handleGenericError method and makeError call so the response content is
sanitized while retaining internal logs.

In `@packages/server/src/api/rest/openapi.ts`:
- Around line 73-89: generatePaths is currently adding collection and
single-resource paths unconditionally, causing operations like DELETE to be
included despite excludedOperations; update generatePaths to check
isOperationIncluded(modelName, operation) before adding each operation and when
calling buildCollectionPath and buildSinglePath so only allowed operations are
emitted—specifically, in generatePaths use isOperationIncluded for collection
ops (e.g., 'list','create') before assigning paths[`/${modelPath}`] and for
single-resource ops (e.g., 'read','update','delete') before assigning
paths[`/${modelPath}/{id}`]; also ensure the lower-level builders
(buildCollectionPath and buildSinglePath) accept or consult operation filters
(or are called selectively) so lines in the broader generation block (the later
code around 136-250) follow the same inclusion checks.
- Around line 191-250: The OpenAPI generator (buildSinglePath) is missing
operations that the runtime serves: add a PUT operation for /{model}/{id}
(operationId `replace<Model>` or `put<Model>` to match naming) with the same
requestBody schema as PATCH (`${modelName}UpdateRequest`) and appropriate
200/400/404 responses, and ensure relationship DELETE paths are generated for
/{model}/{id}/relationships/{field} (operationId like
`delete${modelName}Relationship${Field}`) returning 200/404; mirror the runtime
behavior found in RestApiHandler.handleRequest (lines handling PUT and DELETE
relationship) when creating these entries so the OpenAPI spec matches the live
endpoints.
- Around line 139-400: The responses currently reference
components/schemas/_errorResponse directly (invalid because Response Object refs
must target components/responses and Response Objects need a description);
update every occurrence inside buildCollectionPath (list/create),
buildSinglePath (get/patch/delete), buildFetchRelatedPath (get),
buildRelationshipPath (get/put/patch/post) and buildProcedureOperation to either
reference a Response Object at '#/components/responses/_errorResponse' or
replace the inline entry with a proper Response Object that includes a
description and content whose schema points to
'#/components/schemas/_errorResponse' (e.g. '400': { $ref:
'#/components/responses/_errorResponse' } or '400': { description: 'Error',
content: { 'application/vnd.api+json': { schema: { $ref:
'#/components/schemas/_errorResponse' } } } }); ensure every response entry that
previously used the schema $ref is converted accordingly.

In `@packages/server/src/api/rpc/openapi.ts`:
- Around line 73-98: The writeOps list includes ops createManyAndReturn and
updateManyAndReturn that cause references to
`${modelName}CreateManyAndReturnArgs` / `${modelName}UpdateManyAndReturnArgs`
but generateSchemas() never registers those schemas; update the generation logic
so either (A) remove those ops from writeOps or (B) add schema generation for
those args by extending opToArgsSchema/op mapping and ensure generateSchemas()
creates `${modelName}CreateManyAndReturnArgs` and
`${modelName}UpdateManyAndReturnArgs` (or maps them to existing
CreateMany/UpdateMany args) so buildPostOperation/buildPatchOperation produce
valid request-body component refs and no dangling refs. Ensure to touch
writeOps, opToArgsSchema, generateSchemas, and where
buildPostOperation/buildPatchOperation are invoked to keep names consistent.
- Around line 477-495: The recursive filter refs in the logical combinators
(properties['AND'], properties['OR'], properties['NOT']) point to
`#/components/schemas/_${_modelName}WhereInput` which doesn't match what
generateSchemas() emits (`${modelName}WhereInput`), causing dangling refs;
update those three $ref strings to reference the actual schema name produced by
generateSchemas (e.g., `#/components/schemas/${modelName}WhereInput`) or
otherwise make generateSchemas() emit the underscored name so the names match
(modify the ref strings in openapi.ts around the AND/OR/NOT blocks or adjust
generateSchemas() schema names accordingly).
- Around line 171-181: The response entries currently use a response-level $ref
to a Schema Object (e.g. { $ref: '#/components/schemas/_ErrorResponse' }) which
is invalid; update the responses in the OpenAPI generation (the responses object
built alongside modelName and op in packages/server/src/api/rpc/openapi.ts) to
either reference a Response Object (change to { $ref:
'#/components/responses/_ErrorResponse' } and add a
components.responses._ErrorResponse entry that points to the error schema) or
replace the direct $ref with a proper Response Object (include description and
content: application/json with schema: { $ref:
'#/components/schemas/_ErrorResponse' }); apply this change for all occurrences
of the error response generation so validators and generators target
`#/components/responses/` instead of `#/components/schemas/`.
- Around line 63-99: The generated OpenAPI paths always use the single-model
response schema `${modelName}Response`, causing client deserialization errors
for operations that return arrays, counts, aggregates or booleans; update the
path-building loop so each operation uses the correct response schema instead of
`${modelName}Response`. Add an op→response-schema mapping (e.g., findMany ->
`${modelName}[]` or `${modelName}ArrayResponse`,
count/createMany/updateMany/deleteMany -> `${modelName}BatchResponse` or a count
schema, aggregate -> `${modelName}AggregateResponse`, groupBy ->
`${modelName}GroupByResponse`, exists -> `${modelName}ExistsResponse`), and pass
that schema name into the builders (or extend
buildGetOperation/buildPostOperation/buildPatchOperation/buildDeleteOperation to
accept a responseSchemaName). Use the existing symbols opToArgsSchema,
buildGetOperation, buildPostOperation, buildPatchOperation,
buildDeleteOperation, isOperationIncluded and queryOptions to locate where to
inject the response schema selection and adjust the builders to reference the
provided response schema when constructing responses.

In
`@packages/server/test/openapi/migrated/baseline/rest-type-coverage-3.1.0.baseline.yaml`:
- Line 549: Fix the typo in the OpenAPI baseline description under the _links
schema: change the description string "Link for refetching the curent results"
to "Link for refetching the current results" so the word "curent" is corrected
to "current" in the description field.
- Around line 609-610: The YAML description for the schema property named
"zodErrors" has an unintended line break so the word "failure" is split onto its
own line; fix it by merging the lines into a single properly quoted or block
scalar YAML value (e.g., use a single-line string or a pipe/greater-than block)
so the description reads continuously as "Zod validation errors if the error is
due to data validation failure", and update the "description" value for the
zodErrors entry accordingly.

---

Nitpick comments:
In `@packages/server/src/api/index.ts`:
- Around line 1-3: Add re-exports for the concrete spec generator classes so
external consumers can import them from the main barrel; specifically export
RestApiSpecGenerator and RPCApiSpecGenerator from the module by adding "export {
RestApiSpecGenerator } from './rest';" and "export { RPCApiSpecGenerator } from
'./rpc';" alongside the existing handler and type exports (keeping the existing
RestApiHandler, RPCApiHandler, and OpenApiSpecGenerator/OpenApiSpecOptions
exports).

In `@packages/server/test/openapi/migrated/rest-migrated.test.ts`:
- Around line 198-208: The test "baseline comparison - rest-3.1.0" currently
uses an early return after checking fs.existsSync(path.join(...)) which silently
passes when the baseline is missing; change this to use Vitest's skip mechanism
(e.g., it.skip or it.skipIf) so the test is explicitly marked skipped instead of
returning early—remove the console.warn and early return, and wrap or declare
the test using it.skipIf(!fs.existsSync(path.join(__dirname, 'baseline',
'rest-3.1.0.baseline.yaml'))) while keeping the body that reads
YAML.parse(fs.readFileSync(...)) and the existing expects intact.

In `@packages/server/test/openapi/migrated/rpc-migrated.test.ts`:
- Around line 165-197: The three tests ('baseline comparison - rpc-3.1.0',
'baseline comparison - rpc-3.1.0-omit', 'baseline comparison -
rpc-type-coverage-3.1.0') currently early-return with console.warn when baseline
files are missing; replace that pattern with Vitest's skip so missing baselines
show as skipped tests. For each test, compute baselinePath and then do: if
(!fs.existsSync(baselinePath)) { it.skip('<same test title>', () => {}); return;
} or better yet choose the test function dynamically (const maybeIt =
fs.existsSync(baselinePath) ? it : it.skip) and call maybeIt('<same test
title>', async () => { const baseline = YAML.parse(...); expect(...); });
ensuring the test name and assertions (YAML.parse and expect lines) remain
unchanged.
- Around line 128-129: Remove the unnecessary `as any` casts on the OpenAPI info
assertions: replace `expect((spec.info as any).description).toBe('A custom
API')` and `expect((spec.info as any).summary).toBe('awesome api')` with
`expect(spec.info.description).toBe('A custom API')` and
`expect(spec.info.summary).toBe('awesome api')`; ensure `spec` is typed as an
OpenAPIV3_1.Document (or otherwise that `spec.info` is recognized) so the
`description` and `summary` properties on `spec.info` are accepted by TypeScript
without casting.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: f75a29d7-1077-45f0-bb75-c14f1dc714ee

📥 Commits

Reviewing files that changed from the base of the PR and between 00768de and 59e3c3a.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (24)
  • packages/server/package.json
  • packages/server/src/api/common/spec-utils.ts
  • packages/server/src/api/common/types.ts
  • packages/server/src/api/index.ts
  • packages/server/src/api/rest/handler.ts
  • packages/server/src/api/rest/index.ts
  • packages/server/src/api/rest/openapi.ts
  • packages/server/src/api/rpc/handler.ts
  • packages/server/src/api/rpc/index.ts
  • packages/server/src/api/rpc/openapi.ts
  • packages/server/test/openapi/baseline/rest-3.1.0.baseline.yaml
  • packages/server/test/openapi/baseline/rest-type-coverage-3.1.0.baseline.yaml
  • packages/server/test/openapi/baseline/rpc-3.1.0-omit.baseline.yaml
  • packages/server/test/openapi/baseline/rpc-3.1.0.baseline.yaml
  • packages/server/test/openapi/baseline/rpc-type-coverage-3.1.0.baseline.yaml
  • packages/server/test/openapi/migrated/baseline/rest-3.1.0.baseline.yaml
  • packages/server/test/openapi/migrated/baseline/rest-type-coverage-3.1.0.baseline.yaml
  • packages/server/test/openapi/migrated/baseline/rpc-3.1.0-omit.baseline.yaml
  • packages/server/test/openapi/migrated/baseline/rpc-3.1.0.baseline.yaml
  • packages/server/test/openapi/migrated/baseline/rpc-type-coverage-3.1.0.baseline.yaml
  • packages/server/test/openapi/migrated/rest-migrated.test.ts
  • packages/server/test/openapi/migrated/rpc-migrated.test.ts
  • packages/server/test/openapi/rest-openapi.test.ts
  • packages/server/test/openapi/rpc-openapi.test.ts

@claude
Copy link
Contributor

claude bot commented Mar 19, 2026

Claude finished @ymc9's task —— View job


Code Review for OpenAPI Spec Generation PR

I'm conducting a comprehensive review of this PR that adds OpenAPI spec generation with filter kind slicing support. Here's my review progress:

Review Tasks:

  • Analyze architectural changes and new class structure
  • Review filter kind slicing implementation
  • Examine test coverage and quality
  • Check for potential bugs and edge cases
  • Review security considerations
  • Assess performance implications
  • Verify OpenAPI specification compliance

Major Strengths

Excellent Architecture & Design

  • Clean separation of concerns: Extracting OpenAPI spec generation into dedicated RestApiSpecGenerator and RPCApiSpecGenerator classes is a solid architectural decision
  • Shared utilities: The spec-utils.ts module provides excellent reusable functions for inclusion/exclusion logic
  • Interface-based design: OpenApiSpecGenerator interface enables extensibility and proper testing
  • Proper Zod validation: Good input validation for handler options

Robust Filter Kind Slicing Implementation

  • Well-designed inclusion/exclusion logic: isFilterKindIncluded function properly handles both includedFilterKinds and excludedFilterKinds with correct precedence
  • Proper fallback hierarchy: Model-specific → $all model → global defaults works as expected
  • Comprehensive filter coverage: Supports all four filter kinds (Equality, Like, Range, List) with appropriate type constraints

Strong Test Coverage

  • Comprehensive test suite: 4 test files with 10 baseline YAML fixtures
  • Migration validation: Proper migration from zenstack-v2 with baseline comparisons
  • Good test scenarios: Tests cover both REST and RPC approaches with various configurations

⚠️ Critical Security Issues

🔴 CRITICAL: Stack Trace Exposure

Location: packages/server/src/api/rest/index.ts:511

private handleGenericError(err: unknown): Response | PromiseLike<Response> {
    return this.makeError('unknownError', err instanceof Error ? `${err.message}\n${err.stack}` : 'Unknown error');
}

Risk: This exposes full stack traces in 500 responses, leaking internal server paths, code structure, and potentially sensitive information to clients.

Impact: High - Information disclosure vulnerability

Fix Required: Remove stack traces from responses, log them internally only.


🔴 Critical OpenAPI Specification Violations

1. Invalid Response References in RPC Generator

Location: packages/server/src/api/rpc/openapi.ts:180, 212, 244, 276, 288

'400': { $ref: '#/components/schemas/_ErrorResponse' }

Issue: Per OpenAPI 3.1 spec, response-level $ref must target components/responses, not components/schemas. Response Objects also require a description field.

2. Broken Self-References in WhereInput Schemas

Location: packages/server/src/api/rpc/openapi.ts:480-492

properties['AND'] = {
    oneOf: [
        { $ref: `#/components/schemas/_${_modelName}WhereInput` },  // ❌ Wrong name

Issue: References _${modelName}WhereInput but generateSchemas() creates ${modelName}WhereInput, causing dangling references.

3. Missing Schema Definitions

Location: packages/server/src/api/rpc/openapi.ts:77, 81
Issue: createManyAndReturn and updateManyAndReturn operations reference ${modelName}CreateManyAndReturnArgs / ${modelName}UpdateManyAndReturnArgs but these schemas are never generated in generateSchemas().

4. Wrong Response Schema Types

Location: packages/server/src/api/rpc/openapi.ts:176
Issue: All operations use ${modelName}Response schema, but operations like findMany, count, aggregate return arrays/batch objects, not single models. This will cause client deserialization failures.


🟠 Major Logic Issues

1. Filter Kind Slicing Logic Bug

Location: packages/server/src/api/common/spec-utils.ts:29, 85

const modelSlicing = (slicing.models as Record<string, any>)[modelKey] ?? (slicing.models as any).$all;

Issue: Uses ?? operator which completely replaces $all rules when model-specific config exists. Global $all rules should be merged with model-specific overrides, not replaced.

Impact: Users lose global configuration when any model-specific settings are applied.

2. REST Generator Ignores Operation Filtering

Location: packages/server/src/api/rest/openapi.ts:84-88
Issue: Collection and single-resource paths are generated unconditionally, ignoring excludedOperations/includedOperations from queryOptions.slicing.

3. Inconsistent ID Handling in processUpdate

Location: packages/server/src/api/rest/index.ts:1280-1293
Issue: Manual construction of relation payloads instead of using existing makeIdConnect() function, causing numeric IDs to remain as strings and compound IDs to use different formats.


🟡 Minor Issues

1. Missing Documentation

  • No JSDoc for complex filter kind logic in spec generators
  • Missing exports in main barrel module for concrete generator classes

2. Typos in Baseline Files

  • Line 549: "curent" → "current" in REST type coverage baseline
  • Lines 609-610: Broken YAML multiline description for "zodErrors"

3. Test Improvements Needed

  • Tests use early returns instead of it.skip() for missing baselines
  • Unnecessary as any casts in RPC test assertions

🎯 Performance Considerations

Good:

  • Proper caching of serializers and model metadata
  • Efficient schema generation with reusable components

Areas for Optimization:

  • Repeated model lookups in OpenAPI generation could be cached
  • String operations in filter generation loops could benefit from memoization

🔒 Other Security Considerations

Input Validation:

  • ✅ Good Zod validation for handler options
  • ⚠️ queryOptions validation could be more specific (currently z.object().optional())

Type Safety:

  • ⚠️ Filter kind strings are magic strings - could benefit from const assertions
  • ✅ Good use of TypeScript for schema definitions

📋 Recommendations

Must Fix (Critical):

  1. Remove stack traces from error responses (security risk)
  2. Fix OpenAPI spec violations - wrong response references, missing schemas, broken refs
  3. Implement operation filtering in REST generator
  4. Fix filter kind slicing logic to properly merge $all with model-specific rules

Should Fix (Major):

  1. Use makeIdConnect() consistently for relation payloads
  2. Add missing response schema variants for different operation types
  3. Improve input validation specificity

Nice to Have:

  1. Add JSDoc documentation for complex logic
  2. Export concrete generator classes from main module
  3. Fix typos and test improvements

Overall Assessment

This is a significant and valuable addition to the codebase with excellent architectural decisions. The filter kind slicing feature is well-designed and the test coverage is comprehensive. However, several critical issues must be addressed before merging:

  • Security: Stack trace exposure is a significant risk
  • Specification Compliance: Multiple OpenAPI violations will break client generation
  • Logic Correctness: Filter kind slicing and operation filtering bugs affect core functionality

Recommended Action: Address critical and major issues before merge. The foundation is solid but needs these important fixes.


@claude
Copy link
Contributor

claude bot commented Mar 19, 2026

Claude finished @ymc9's task —— View job


Code Review for OpenAPI Spec Generation PR

I've conducted a comprehensive review of this PR that adds OpenAPI spec generation with filter kind slicing support. Here's my detailed feedback:

Review Tasks:

  • Analyze architectural changes and new class structure
  • Review filter kind slicing implementation
  • Examine test coverage and quality
  • Check for potential bugs and edge cases
  • Review security considerations
  • Assess performance implications
  • Verify OpenAPI specification compliance

Major Strengths

Excellent Architecture & Design

  • Clean separation of concerns: Extracting OpenAPI spec generation into dedicated RestApiSpecGenerator and RPCApiSpecGenerator classes is a solid architectural decision
  • Shared utilities: The spec-utils.ts module provides excellent reusable functions for inclusion/exclusion logic
  • Interface-based design: OpenApiSpecGenerator interface enables extensibility and proper testing
  • Proper Zod validation: Good input validation for handler options

Robust Filter Kind Slicing Implementation

  • Well-designed inclusion/exclusion logic: isFilterKindIncluded function properly handles both includedFilterKinds and excludedFilterKinds with correct precedence
  • Proper fallback hierarchy: Model-specific → $all model → global defaults works as expected
  • Comprehensive filter coverage: Supports all four filter kinds (Equality, Like, Range, List) with appropriate type constraints

Strong Test Coverage

  • Comprehensive test suite: 4 test files with 10 baseline YAML fixtures
  • Migration validation: Proper migration from zenstack-v2 with baseline comparisons
  • Good test scenarios: Tests cover both REST and RPC approaches with various configurations

⚠️ Critical Security Issues

🔴 CRITICAL: Stack Trace Exposure

Location: packages/server/src/api/rest/index.ts:511

private handleGenericError(err: unknown): Response | PromiseLike<Response> {
    return this.makeError('unknownError', err instanceof Error ? `${err.message}\n${err.stack}` : 'Unknown error');
}

Risk: This exposes full stack traces in 500 responses, leaking internal server paths, code structure, and potentially sensitive information to clients.

Impact: High - Information disclosure vulnerability

Fix Required: Remove stack traces from responses, log them internally only.


🔴 Critical OpenAPI Specification Violations

1. Invalid Response References in RPC Generator

Location: packages/server/src/api/rpc/openapi.ts:180, 212, 244, 276, 288

'400': { $ref: '#/components/schemas/_ErrorResponse' }

Issue: Per OpenAPI 3.1 spec, response-level $ref must target components/responses, not components/schemas. Response Objects also require a description field.

2. Broken Self-References in WhereInput Schemas

Location: packages/server/src/api/rpc/openapi.ts:480-492

properties['AND'] = {
    oneOf: [
        { $ref: `#/components/schemas/_${_modelName}WhereInput` },  // ❌ Wrong name

Issue: References _${modelName}WhereInput but generateSchemas() creates ${modelName}WhereInput, causing dangling references.

3. Missing Schema Definitions

Location: packages/server/src/api/rpc/openapi.ts:77, 81
Issue: createManyAndReturn and updateManyAndReturn operations reference ${modelName}CreateManyAndReturnArgs / ${modelName}UpdateManyAndReturnArgs but these schemas are never generated in generateSchemas().

4. Wrong Response Schema Types

Location: packages/server/src/api/rpc/openapi.ts:176
Issue: All operations use ${modelName}Response schema, but operations like findMany, count, aggregate return arrays/batch objects, not single models. This will cause client deserialization failures.


🟠 Major Logic Issues

1. Filter Kind Slicing Logic Bug

Location: packages/server/src/api/common/spec-utils.ts:29, 85

const modelSlicing = (slicing.models as Record<string, any>)[modelKey] ?? (slicing.models as any).$all;

Issue: Uses ?? operator which completely replaces $all rules when model-specific config exists. Global $all rules should be merged with model-specific overrides, not replaced.

Impact: Users lose global configuration when any model-specific settings are applied.

2. REST Generator Ignores Operation Filtering

Location: packages/server/src/api/rest/openapi.ts:84-88
Issue: Collection and single-resource paths are generated unconditionally, ignoring excludedOperations/includedOperations from queryOptions.slicing.

3. Inconsistent ID Handling in processUpdate

Location: packages/server/src/api/rest/index.ts:1280-1293
Issue: Manual construction of relation payloads instead of using existing makeIdConnect() function, causing numeric IDs to remain as strings and compound IDs to use different formats.


🟡 Minor Issues

1. Missing Documentation

  • No JSDoc for complex filter kind logic in spec generators
  • Missing exports in main barrel module for concrete generator classes

2. Typos in Baseline Files

  • Line 549: "curent" → "current" in REST type coverage baseline
  • Lines 609-610: Broken YAML multiline description for "zodErrors"

3. Test Improvements Needed

  • Tests use early returns instead of it.skip() for missing baselines
  • Unnecessary as any casts in RPC test assertions

🎯 Performance Considerations

Good:

  • Proper caching of serializers and model metadata
  • Efficient schema generation with reusable components

Areas for Optimization:

  • Repeated model lookups in OpenAPI generation could be cached
  • String operations in filter generation loops could benefit from memoization

🔒 Other Security Considerations

Input Validation:

  • ✅ Good Zod validation for handler options
  • ⚠️ queryOptions validation could be more specific (currently z.object().optional())

Type Safety:

  • ⚠️ Filter kind strings are magic strings - could benefit from const assertions
  • ✅ Good use of TypeScript for schema definitions

📋 Recommendations

Must Fix (Critical):

  1. Remove stack traces from error responses (security risk)
  2. Fix OpenAPI spec violations - wrong response references, missing schemas, broken refs
  3. Implement operation filtering in REST generator
  4. Fix filter kind slicing logic to properly merge $all with model-specific rules

Should Fix (Major):

  1. Use makeIdConnect() consistently for relation payloads
  2. Add missing response schema variants for different operation types
  3. Improve input validation specificity

Nice to Have:

  1. Add JSDoc documentation for complex logic
  2. Export concrete generator classes from main module
  3. Fix typos and test improvements

Overall Assessment

This is a significant and valuable addition to the codebase with excellent architectural decisions. The filter kind slicing feature is well-designed and the test coverage is comprehensive. However, several critical issues must be addressed before merging:

  • Security: Stack trace exposure is a significant risk
  • Specification Compliance: Multiple OpenAPI violations will break client generation
  • Logic Correctness: Filter kind slicing and operation filtering bugs affect core functionality

Recommended Action: Address critical and major issues before merge. The foundation is solid but needs these important fixes.


Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

♻️ Duplicate comments (3)
packages/server/src/api/rest/openapi.ts (3)

73-89: ⚠️ Potential issue | 🟠 Major

Honor operation slicing before publishing REST methods.

generatePaths() always assigns both collection and single-resource path items, and the builders always return every method, so includedOperations / excludedOperations can't remove get, post, patch, or delete from the emitted spec. Gate each method with isOperationIncluded(...) and skip empty path items.

Also applies to: 136-188, 191-250

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/server/src/api/rest/openapi.ts` around lines 73 - 89, generatePaths
currently unconditionally adds collection and single-resource PathItems by
calling buildCollectionPath and buildSinglePath which themselves always include
all methods; modify generatePaths and the builders (buildCollectionPath,
buildSinglePath) to check isOperationIncluded(modelName, operation) before
adding each operation (e.g., "get", "post", "patch", "delete") to the PathItem
so omitted operations are not emitted, and ensure generatePaths skips adding a
PathItem if it ends up empty after filtering; update logic used by
getIncludedModels/getModelPath as needed so only included operations produce
entries in the final PathsObject.

150-160: ⚠️ Potential issue | 🔴 Critical

Use Response Objects for error statuses.

Every 400 / 404 entry here points directly at #/components/schemas/_errorResponse. Under OpenAPI 3.1, a status-code response must be a Response Object or a Reference Object to components/responses; schema refs only belong under content.*.schema. As written, these entries are invalid and also omit the required description.

#!/bin/bash
set -euo pipefail

file="packages/server/src/api/rest/openapi.ts"

echo "Direct schema refs under response status codes:"
rg -n "'(400|404)':\s*\{\s*\$ref:\s*'#/components/schemas/_errorResponse'" "$file"

echo
echo "Expected: no matches after wrapping these as Response Objects or moving them to components.responses."

Also applies to: 175-185, 199-209, 225-236, 244-247, 278-281, 308-314, 325-328, 339-342, 360-363, 375-378

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/server/src/api/rest/openapi.ts` around lines 150 - 160, The response
entries for error status codes (e.g., the '400' and '404' entries next to
modelName in openapi.ts) incorrectly reference a schema
(`#/components/schemas/_errorResponse`) directly instead of using a Response
Object; update each such status-code entry to be either a Reference Object to
components.responses (e.g., $ref: '#/components/responses/_errorResponse') or a
full Response Object that includes a required description and a content block
whose schema references `#/components/schemas/_errorResponse`, and apply the same
change to all occurrences flagged (near the blocks that construct responses for
modelNameListResponse and other endpoints). Ensure each response entry includes
description when not using a $ref and replace direct schema refs under the
status keys with the proper responses structure.

91-113: ⚠️ Potential issue | 🟠 Major

Relation slicing still leaks endpoints and dangling refs.

generatePaths() never checks isFieldOmitted(modelName, fieldName, this.queryOptions), so omitted relation fields still get /{field} and /relationships/{field} paths. Separately, buildModelReadSchema() can still emit $ref: '#/components/schemas/${fieldDef.type}' for a kept relation whose target model was sliced out, which leaves a dangling ref because that component is never generated.

Also applies to: 675-680

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/server/src/api/rest/openapi.ts` around lines 91 - 113,
generatePaths() currently adds relation endpoints without checking
isFieldOmitted and can refer to relation target schemas that were sliced out;
update the loop over modelDef.fields in generatePaths() to skip any field where
isFieldOmitted(modelName, fieldName, this.queryOptions) returns true and also
ensure that before adding a relation path you verify the relation target model
still exists in this.schema.models (reuse the existing relModelDef check) and
that relModelDef was not sliced out; additionally, update buildModelReadSchema()
to avoid emitting $ref to fieldDef.type when that target model has been omitted
(check this.schema.models[fieldDef.type] or a slice/keep predicate) and emit an
appropriate inline schema or remove the relation property instead to prevent
dangling $ref; change references in generatePaths() to only call
buildFetchRelatedPath and buildRelationshipPath when the field is not omitted
and the target model is present.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/server/src/api/rest/openapi.ts`:
- Around line 253-284: In buildFetchRelatedPath, the 200 response currently only
has a description; update the responses object so the '200' entry includes a
proper schema $ref: use `#/components/schemas/${fieldDef.type}ListResponse` when
fieldDef.array is true (collection) and
`#/components/schemas/${fieldDef.type}Response` when false (singular). Locate
the method buildFetchRelatedPath and modify the returned get.responses['200'] to
point at the correct schema based on isCollection (using
modelName/fieldName/fieldDef to determine the type).
- Around line 671-691: buildModelReadSchema currently emits fields at the top
level which causes the later allOf merge with the _resource wrapper to put
required attributes and relation refs at the wrong level; update
buildModelReadSchema so it returns an object with type: 'object' and two
properties: attributes (an object whose properties are the non-relation scalar
fields produced via fieldToSchema, with required moved into attributes.required)
and relationships (an object whose properties are the relation fields as
ReferenceObject refs to related model schemas), keeping existing checks
isFieldOmitted, fieldDef.omit, and this.queryOptions; ensure any array/optional
logic remains but that required only lists attribute names under
attributes.required and relation fields are only referenced under relationships
rather than at the top level.
- Around line 694-757: The builders (buildCreateRequestSchema and the similar
Update builder) currently skip fields only by fieldDef.omit but don't respect
per-request field slicing; modify buildCreateRequestSchema (and the Update
counterpart) to call isFieldOmitted(_modelName, fieldName, modelDef) for each
field and skip the field when it returns true (in addition to existing checks
like fieldDef.omit/updatedAt/foreignKeyFor/id with default); ensure you actually
use the _modelName parameter passed into buildCreateRequestSchema and mirror the
same change in the update schema builder so fields removed via queryOptions are
not included in CreateRequest/UpdateRequest.

In `@packages/server/src/api/rpc/openapi.ts`:
- Around line 401-412: The loop over modelDef.fields used by the
CreateInput/UpdateInput builders calls typeToSchema(fieldDef.type) directly,
which loses nullability and array shape; update the logic in the
CreateInput/UpdateInput builders (the block that fills properties and required)
to: 1) detect when fieldDef.type represents an array and set
properties[fieldName] to an OpenAPI array schema with items produced by
typeToSchema(baseType) instead of passing the array type through unchanged; 2)
preserve nullable by setting properties[fieldName].nullable = true when the
field type allows null (or by merging/using the nullable flag returned from
typeToSchema for the inner/base type); and 3) adjust the required calculation to
include non-optional array fields (remove the current !fieldDef.array guard) so
list columns that are required remain required; ensure you reference and update
the same symbols used now (typeToSchema, modelDef.fields, properties, required)
so CreateInput and UpdateInput both get the fix.
- Around line 380-386: The generator is marking non-array, non-optional relation
fields as required which is incorrect; update the required-pushing logic in the
loop over modelDef.fields (the block using isFieldOmitted, fieldToSchema and
this.queryOptions) to skip pushing relation fields into required—i.e., add a
relation-detection check (e.g., detect relation/object-kind fields via
fieldDef.kind/type/relationship flag used in your schema representation) and
change the condition to: if (!fieldDef.optional && !fieldDef.array &&
!isRelationField(fieldDef)) { required.push(fieldName); } so relation payloads
remain optional unless explicitly selected/included.
- Around line 688-735: buildFieldFilterSchema currently uses baseSchema =
this.typeToSchema(fieldDef.type) which strips array and optional wrappers so
filters like equals/in/notIn accept only the element type and cannot represent
null or arrays; change it to build a wrapped schema that respects fieldDef.array
and fieldDef.optional (e.g. if fieldDef.array wrap baseSchema into an array
schema for equals/in/notIn where appropriate, and if fieldDef.optional mark the
schema as nullable or include null in the type) and use that wrapped schema for
filterProps entries such as 'equals', 'not', 'in', 'notIn' (while keeping
element-level schemas for list operators like 'has'/'hasEvery'/'hasSome' if
intended); update references to this.typeToSchema, fieldDef.array, and
fieldDef.optional inside buildFieldFilterSchema to produce and use
baseSchemaWithWrappers instead of the bare baseSchema.

---

Duplicate comments:
In `@packages/server/src/api/rest/openapi.ts`:
- Around line 73-89: generatePaths currently unconditionally adds collection and
single-resource PathItems by calling buildCollectionPath and buildSinglePath
which themselves always include all methods; modify generatePaths and the
builders (buildCollectionPath, buildSinglePath) to check
isOperationIncluded(modelName, operation) before adding each operation (e.g.,
"get", "post", "patch", "delete") to the PathItem so omitted operations are not
emitted, and ensure generatePaths skips adding a PathItem if it ends up empty
after filtering; update logic used by getIncludedModels/getModelPath as needed
so only included operations produce entries in the final PathsObject.
- Around line 150-160: The response entries for error status codes (e.g., the
'400' and '404' entries next to modelName in openapi.ts) incorrectly reference a
schema (`#/components/schemas/_errorResponse`) directly instead of using a
Response Object; update each such status-code entry to be either a Reference
Object to components.responses (e.g., $ref:
'#/components/responses/_errorResponse') or a full Response Object that includes
a required description and a content block whose schema references
`#/components/schemas/_errorResponse`, and apply the same change to all
occurrences flagged (near the blocks that construct responses for
modelNameListResponse and other endpoints). Ensure each response entry includes
description when not using a $ref and replace direct schema refs under the
status keys with the proper responses structure.
- Around line 91-113: generatePaths() currently adds relation endpoints without
checking isFieldOmitted and can refer to relation target schemas that were
sliced out; update the loop over modelDef.fields in generatePaths() to skip any
field where isFieldOmitted(modelName, fieldName, this.queryOptions) returns true
and also ensure that before adding a relation path you verify the relation
target model still exists in this.schema.models (reuse the existing relModelDef
check) and that relModelDef was not sliced out; additionally, update
buildModelReadSchema() to avoid emitting $ref to fieldDef.type when that target
model has been omitted (check this.schema.models[fieldDef.type] or a slice/keep
predicate) and emit an appropriate inline schema or remove the relation property
instead to prevent dangling $ref; change references in generatePaths() to only
call buildFetchRelatedPath and buildRelationshipPath when the field is not
omitted and the target model is present.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: d8ec89b2-c972-4703-b6c5-f4719db96185

📥 Commits

Reviewing files that changed from the base of the PR and between ff5735f and 8eea293.

📒 Files selected for processing (2)
  • packages/server/src/api/rest/openapi.ts
  • packages/server/src/api/rpc/openapi.ts

…n OpenAPI

Extract model description from @@meta("description", "...") attribute and
apply it to the generated schema object in both REST and RPC OpenAPI generators.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@claude
Copy link
Contributor

claude bot commented Mar 19, 2026

Claude encountered an error —— View job


I'll analyze this and get back to you.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (7)
packages/server/src/api/rpc/openapi.ts (7)

482-497: ⚠️ Potential issue | 🔴 Critical

Recursive WhereInput refs never resolve.

generateSchemas() only registers ${modelName}WhereInput, but AND/OR/NOT point at #/components/schemas/_${modelName}WhereInput. Every recursive filter schema will contain dangling refs.

Suggested fix
-                { $ref: `#/components/schemas/_${_modelName}WhereInput` },
-                { type: 'array', items: { $ref: `#/components/schemas/_${_modelName}WhereInput` } },
+                { $ref: `#/components/schemas/${_modelName}WhereInput` },
+                { type: 'array', items: { $ref: `#/components/schemas/${_modelName}WhereInput` } },
...
-            items: { $ref: `#/components/schemas/_${_modelName}WhereInput` },
+            items: { $ref: `#/components/schemas/${_modelName}WhereInput` },
...
-                { $ref: `#/components/schemas/_${_modelName}WhereInput` },
-                { type: 'array', items: { $ref: `#/components/schemas/_${_modelName}WhereInput` } },
+                { $ref: `#/components/schemas/${_modelName}WhereInput` },
+                { type: 'array', items: { $ref: `#/components/schemas/${_modelName}WhereInput` } },
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/server/src/api/rpc/openapi.ts` around lines 482 - 497, The recursive
WhereInput refs are pointing to `#/components/schemas/_`${modelName}WhereInput
while generateSchemas() registers ${modelName}WhereInput, leaving dangling refs;
update the schema registration in generateSchemas() to also register the
underscored name (register a schema under _${modelName}WhereInput) or change the
AND/OR/NOT refs in the block that sets properties['AND'], properties['OR'],
properties['NOT'] to point to `#/components/schemas/`${modelName}WhereInput so the
references match—locate generateSchemas() and the properties['AND'|'OR'|'NOT']
lines to make the names consistent.

75-98: ⚠️ Potential issue | 🔴 Critical

createManyAndReturn and updateManyAndReturn reference request components that are never generated.

opToArgsSchema() resolves these ops to ${modelName}CreateManyAndReturnArgs and ${modelName}UpdateManyAndReturnArgs, but generateSchemas() never registers either component. The resulting OpenAPI will contain dangling request-body refs for both endpoints unless these ops are mapped to existing args schemas or dedicated schemas are emitted.

Also applies to: 155-157, 350-354


127-130: ⚠️ Potential issue | 🔴 Critical

Response-level $ref is pointing at the wrong component type.

These 400 entries sit inside a Responses Object, so a $ref here has to resolve to a Response Object, not #/components/schemas/_ErrorResponse. Validators and codegen will reject the document until this is wrapped as a real response or moved under components.responses.

Suggested fix pattern
- '400': { $ref: '#/components/schemas/_ErrorResponse' },
+ '400': {
+     description: 'Bad request',
+     content: {
+         'application/json': {
+             schema: { $ref: '#/components/schemas/_ErrorResponse' },
+         },
+     },
+ },
In OpenAPI 3.1, when a response entry inside `responses` uses `$ref`, can it target `#/components/schemas/...`, or must it reference a Response Object under `#/components/responses/...`?

Also applies to: 172-182, 204-214, 236-246, 268-278, 287-290

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/server/src/api/rpc/openapi.ts` around lines 127 - 130, The responses
objects currently use a $ref that points to a schema (e.g. '400': { $ref:
'#/components/schemas/_ErrorResponse' }) which is invalid because response-level
$ref must resolve to a Response Object; update each offending entry (the '400'
response refs and the other similar entries around the other occurrences) to
either reference a Response Object under `#/components/responses` (e.g.
'#/components/responses/_ErrorResponse') or replace the $ref with an inline
Response Object that includes description and content referencing the schema
(e.g. description plus content: application/json with schema: { $ref:
'#/components/schemas/_ErrorResponse' }); ensure you apply this change for every
responses block that currently points directly at the schema.

414-416: ⚠️ Potential issue | 🟠 Major

CreateInput and UpdateInput drop nullable and list field shape.

These builders go straight through typeToSchema(fieldDef.type), so nullable columns are emitted as non-null and list columns are emitted as single values. Line 415 also skips required scalar-list fields because of !fieldDef.array.

Also applies to: 436-436

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/server/src/api/rpc/openapi.ts` around lines 414 - 416,
CreateInput/UpdateInput builders currently call typeToSchema(fieldDef.type)
directly, which loses nullable and array shapes and skips required array fields;
update the property generation so that if fieldDef.array is true you wrap the
element schema into an array schema (type: "array", items:
typeToSchema(fieldDef.type)), and if fieldDef.nullable is true you mark the
schema as nullable or allow null (e.g. set schema.nullable = true or use anyOf
with {type:null}); keep the required push logic independent of fieldDef.array so
non-optional array fields are included in required, and continue to respect
fieldDef.default and fieldDef.optional when deciding required. Ensure these
changes are applied where CreateInput/UpdateInput properties are produced and
where typeToSchema is currently called.

64-99: ⚠️ Potential issue | 🔴 Critical

Use operation-specific response schemas.

All CRUD builders still hardcode ${modelName}Response, whose data field is a single-model ref. That is wrong for findMany, createManyAndReturn, updateManyAndReturn, count, exists, aggregate, groupBy, and the batch ops, so the emitted spec will generate incorrect client types. Plumb a response schema name per op and generate the matching response components instead of reusing the single-model wrapper everywhere.

Also applies to: 172-178, 205-210, 237-242, 269-274, 672-678

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/server/src/api/rpc/openapi.ts` around lines 64 - 99, The code always
uses a single `${modelName}Response` wrapper which yields incorrect types for
multi-item, count, exists, aggregate, groupBy and batch-returning ops; compute
an operation-specific response schema name (e.g., via a new helper like
opToResponseSchema(op, modelName) or a map for ops -> schema names) and pass
that responseSchemaName into the OpenAPI builders instead of relying on
`${modelName}Response`. Concretely, update the loop callers (the GET loop and
the writeOps loop) to compute responseSchemaName and call
this.buildGetOperation(this, ...) / this.buildPostOperation /
this.buildPatchOperation / this.buildDeleteOperation with the extra
responseSchemaName argument, then modify those builder methods
(buildGetOperation, buildPostOperation, buildPatchOperation,
buildDeleteOperation) to accept and use the provided responseSchemaName to
reference and emit correct response components for
findMany/createManyAndReturn/updateManyAndReturn/count/exists/aggregate/groupBy
and batch ops. Ensure the response component generator creates
list/number/boolean/aggregate/grouped shapes as appropriate for each op.

381-386: ⚠️ Potential issue | 🟠 Major

Don't require relation fields in the shared model schema.

select/include make relation payloads conditional, but Line 385 still marks every non-optional relation as required. That makes ordinary RPC responses invalid unless the caller explicitly included the relation.

Suggested fix
-            if (!fieldDef.optional && !fieldDef.array) {
+            if (!fieldDef.optional && !fieldDef.relation) {
                 required.push(fieldName);
             }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/server/src/api/rpc/openapi.ts` around lines 381 - 386, The loop over
modelDef.fields is currently marking every non-optional, non-array field as
required; change the condition that pushes into required (in the block using
fieldDef, modelDef.fields, fieldToSchema, and isFieldOmitted) to also skip
relation fields so relation/select/include-driven payloads are not required;
implement this by detecting relation descriptors on fieldDef (e.g., a relation
flag/property such as fieldDef.relation, fieldDef.targetModel,
fieldDef.isRelation, or other project-specific relation marker) and only push
fieldName into required when the field is non-optional, non-array, and not a
relation.

693-740: ⚠️ Potential issue | 🟠 Major

Filter operators should mirror the field's value schema.

buildFieldFilterSchema() starts from bare typeToSchema(fieldDef.type), so equality/shorthand filters on list fields accept a single element and nullable fields cannot compare against null. Keep an element schema for has/range-style operators, but use a wrapped value schema for equals/not/in/notIn and the shorthand oneOf.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/server/src/api/rpc/openapi.ts` around lines 693 - 740,
buildFieldFilterSchema currently uses baseSchema everywhere so
equality/shorthand filters treat array fields as single elements and nullable
fields cannot be compared to null; fix by deriving a valueSchema that represents
the field's full value (if fieldDef.array then valueSchema = { type: 'array',
items: baseSchema } else valueSchema = baseSchema) and if fieldDef.nullable wrap
valueSchema to allow null (e.g., oneOf [valueSchema, { type: 'null' }]); then
use valueSchema for equals and not, use { type: 'array', items: valueSchema }
for in and notIn, keep element-based baseSchema for list operators
(has/hasEvery/hasSome), and return the shorthand oneOf as { oneOf: [valueSchema,
filterObject] } in buildFieldFilterSchema.
🧹 Nitpick comments (2)
packages/server/test/openapi/rest-openapi.test.ts (2)

78-79: Consider using a proper OpenAPI type for spec.

Using any loses type safety and IDE support. If an OpenAPI type is available from a package like openapi-types, it would help catch assertion errors at compile time.

+import type { OpenAPIV3_1 } from 'openapi-types';
+
 describe('REST OpenAPI spec generation', () => {
     let handler: RestApiHandler;
-    let spec: any;
+    let spec: OpenAPIV3_1.Document;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/server/test/openapi/rest-openapi.test.ts` around lines 78 - 79, The
test declares spec as type any which loses type safety; change spec's type to a
proper OpenAPI type (e.g., OpenAPIV3.Document from the openapi-types package) by
importing the type (import type { OpenAPIV3 } from 'openapi-types') and updating
the declaration (let spec: OpenAPIV3.Document) so assertions against spec in
this file and usage with RestApiHandler are type-checked by TypeScript.

191-203: Consider extracting the parameter name extraction into a helper.

The pattern listOp.parameters.map((p: any) => ('name' in p ? p.name : p.$ref)) is repeated 6 times throughout the file. Extracting it would improve readability and maintainability.

♻️ Proposed helper function

Add near the top of the file:

const getParamNames = (params: any[]) => 
    params.map((p: any) => ('name' in p ? p.name : p.$ref));

Then replace usages:

-const paramNames = listOp.parameters.map((p: any) => ('name' in p ? p.name : p.$ref));
+const paramNames = getParamNames(listOp.parameters);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/server/test/openapi/rest-openapi.test.ts` around lines 191 - 203,
The tests repeat the extraction logic listOp.parameters.map((p: any) => ('name'
in p ? p.name : p.$ref)) so add a small helper (e.g., getParamNames) and replace
direct uses with getParamNames(listOp.parameters) to improve readability;
implement the helper near the top of the test file and update each occurrence
(references: getParamNames, listOp.parameters) to call it.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/server/src/api/rpc/openapi.ts`:
- Around line 406-410: The loops that build
CreateInput/UpdateInput/WhereUniqueInput/WhereInput currently only check
fieldDef.omit and should instead use the same visibility logic as
buildModelOutputSchema; replace checks like "if (fieldDef.omit) continue" with a
call to isFieldOmitted(modelDef, fieldName, queryOptions) (or the existing
isFieldOmitted signature used by buildModelOutputSchema) so that
queryOptions.omit/slicing are honored for request and filter schemas; update the
loops that iterate modelDef.fields (the blocks constructing CreateInput,
UpdateInput, WhereUniqueInput, WhereInput) to call isFieldOmitted before adding
a field.

---

Duplicate comments:
In `@packages/server/src/api/rpc/openapi.ts`:
- Around line 482-497: The recursive WhereInput refs are pointing to
`#/components/schemas/_`${modelName}WhereInput while generateSchemas() registers
${modelName}WhereInput, leaving dangling refs; update the schema registration in
generateSchemas() to also register the underscored name (register a schema under
_${modelName}WhereInput) or change the AND/OR/NOT refs in the block that sets
properties['AND'], properties['OR'], properties['NOT'] to point to
`#/components/schemas/`${modelName}WhereInput so the references match—locate
generateSchemas() and the properties['AND'|'OR'|'NOT'] lines to make the names
consistent.
- Around line 127-130: The responses objects currently use a $ref that points to
a schema (e.g. '400': { $ref: '#/components/schemas/_ErrorResponse' }) which is
invalid because response-level $ref must resolve to a Response Object; update
each offending entry (the '400' response refs and the other similar entries
around the other occurrences) to either reference a Response Object under
`#/components/responses` (e.g. '#/components/responses/_ErrorResponse') or replace
the $ref with an inline Response Object that includes description and content
referencing the schema (e.g. description plus content: application/json with
schema: { $ref: '#/components/schemas/_ErrorResponse' }); ensure you apply this
change for every responses block that currently points directly at the schema.
- Around line 414-416: CreateInput/UpdateInput builders currently call
typeToSchema(fieldDef.type) directly, which loses nullable and array shapes and
skips required array fields; update the property generation so that if
fieldDef.array is true you wrap the element schema into an array schema (type:
"array", items: typeToSchema(fieldDef.type)), and if fieldDef.nullable is true
you mark the schema as nullable or allow null (e.g. set schema.nullable = true
or use anyOf with {type:null}); keep the required push logic independent of
fieldDef.array so non-optional array fields are included in required, and
continue to respect fieldDef.default and fieldDef.optional when deciding
required. Ensure these changes are applied where CreateInput/UpdateInput
properties are produced and where typeToSchema is currently called.
- Around line 64-99: The code always uses a single `${modelName}Response`
wrapper which yields incorrect types for multi-item, count, exists, aggregate,
groupBy and batch-returning ops; compute an operation-specific response schema
name (e.g., via a new helper like opToResponseSchema(op, modelName) or a map for
ops -> schema names) and pass that responseSchemaName into the OpenAPI builders
instead of relying on `${modelName}Response`. Concretely, update the loop
callers (the GET loop and the writeOps loop) to compute responseSchemaName and
call this.buildGetOperation(this, ...) / this.buildPostOperation /
this.buildPatchOperation / this.buildDeleteOperation with the extra
responseSchemaName argument, then modify those builder methods
(buildGetOperation, buildPostOperation, buildPatchOperation,
buildDeleteOperation) to accept and use the provided responseSchemaName to
reference and emit correct response components for
findMany/createManyAndReturn/updateManyAndReturn/count/exists/aggregate/groupBy
and batch ops. Ensure the response component generator creates
list/number/boolean/aggregate/grouped shapes as appropriate for each op.
- Around line 381-386: The loop over modelDef.fields is currently marking every
non-optional, non-array field as required; change the condition that pushes into
required (in the block using fieldDef, modelDef.fields, fieldToSchema, and
isFieldOmitted) to also skip relation fields so relation/select/include-driven
payloads are not required; implement this by detecting relation descriptors on
fieldDef (e.g., a relation flag/property such as fieldDef.relation,
fieldDef.targetModel, fieldDef.isRelation, or other project-specific relation
marker) and only push fieldName into required when the field is non-optional,
non-array, and not a relation.
- Around line 693-740: buildFieldFilterSchema currently uses baseSchema
everywhere so equality/shorthand filters treat array fields as single elements
and nullable fields cannot be compared to null; fix by deriving a valueSchema
that represents the field's full value (if fieldDef.array then valueSchema = {
type: 'array', items: baseSchema } else valueSchema = baseSchema) and if
fieldDef.nullable wrap valueSchema to allow null (e.g., oneOf [valueSchema, {
type: 'null' }]); then use valueSchema for equals and not, use { type: 'array',
items: valueSchema } for in and notIn, keep element-based baseSchema for list
operators (has/hasEvery/hasSome), and return the shorthand oneOf as { oneOf:
[valueSchema, filterObject] } in buildFieldFilterSchema.

---

Nitpick comments:
In `@packages/server/test/openapi/rest-openapi.test.ts`:
- Around line 78-79: The test declares spec as type any which loses type safety;
change spec's type to a proper OpenAPI type (e.g., OpenAPIV3.Document from the
openapi-types package) by importing the type (import type { OpenAPIV3 } from
'openapi-types') and updating the declaration (let spec: OpenAPIV3.Document) so
assertions against spec in this file and usage with RestApiHandler are
type-checked by TypeScript.
- Around line 191-203: The tests repeat the extraction logic
listOp.parameters.map((p: any) => ('name' in p ? p.name : p.$ref)) so add a
small helper (e.g., getParamNames) and replace direct uses with
getParamNames(listOp.parameters) to improve readability; implement the helper
near the top of the test file and update each occurrence (references:
getParamNames, listOp.parameters) to call it.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: e241c9a7-4616-40e5-9a80-757113f92040

📥 Commits

Reviewing files that changed from the base of the PR and between 8eea293 and 1853a04.

📒 Files selected for processing (5)
  • packages/server/src/api/common/spec-utils.ts
  • packages/server/src/api/rest/openapi.ts
  • packages/server/src/api/rpc/openapi.ts
  • packages/server/test/openapi/rest-openapi.test.ts
  • packages/server/test/openapi/rpc-openapi.test.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • packages/server/test/openapi/rpc-openapi.test.ts
  • packages/server/src/api/common/spec-utils.ts
  • packages/server/src/api/rest/openapi.ts

@claude
Copy link
Contributor

claude bot commented Mar 19, 2026

Claude finished @ymc9's task —— View job


Code Review for OpenAPI Spec Generation PR

I've conducted a comprehensive review of this PR that adds OpenAPI spec generation with filter kind slicing support. Here's my detailed feedback:

Review Tasks:

  • Analyze architectural changes and new class structure
  • Review filter kind slicing implementation
  • Examine test coverage and quality
  • Check for potential bugs and edge cases
  • Review security considerations
  • Assess performance implications
  • Verify OpenAPI specification compliance

Major Strengths

Excellent Architecture & Design

  • Clean separation of concerns: Extracting OpenAPI spec generation into dedicated RestApiSpecGenerator and RPCApiSpecGenerator classes is a solid architectural decision
  • Shared utilities: The spec-utils.ts module provides excellent reusable functions for inclusion/exclusion logic
  • Interface-based design: OpenApiSpecGenerator interface enables extensibility and proper testing
  • Proper Zod validation: Good input validation for handler options

Robust Filter Kind Slicing Implementation

  • Well-designed inclusion/exclusion logic: isFilterKindIncluded function properly handles both includedFilterKinds and excludedFilterKinds with correct precedence
  • Proper fallback hierarchy: Model-specific → $all model → global defaults works as expected
  • Comprehensive filter coverage: Supports all four filter kinds (Equality, Like, Range, List) with appropriate type constraints

Strong Test Coverage

  • Comprehensive test suite: 4 test files with 5 baseline YAML fixtures
  • Migration validation: Proper migration from zenstack-v2 with baseline comparisons
  • Good test scenarios: Tests cover both REST and RPC approaches with various configurations

⚠️ Critical Security Issues

🔴 CRITICAL: Stack Trace Exposure

Location: packages/server/src/api/rest/index.ts:511

private handleGenericError(err: unknown): Response | PromiseLike<Response> {
    return this.makeError('unknownError', err instanceof Error ? `${err.message}\n${err.stack}` : 'Unknown error');
}

Risk: This exposes full stack traces in 500 responses, leaking internal server paths, code structure, and potentially sensitive information to clients.

Impact: High - Information disclosure vulnerability

Fix Required: Remove stack traces from responses, log them internally only.


🔴 Critical OpenAPI Specification Violations

1. Invalid Response References in RPC Generator

Location: packages/server/src/api/rpc/openapi.ts:181, 213, 245, 277, 289

'400': { $ref: '#/components/schemas/_ErrorResponse' }

Issue: Per OpenAPI 3.1 spec, response-level $ref must target components/responses, not components/schemas. Response Objects also require a description field.

2. Broken Self-References in WhereInput Schemas

Location: packages/server/src/api/rpc/openapi.ts:485-496

properties['AND'] = {
    oneOf: [
        { $ref: `#/components/schemas/_${_modelName}WhereInput` },  // ❌ Wrong name

Issue: References _${modelName}WhereInput but generateSchemas() creates ${modelName}WhereInput, causing dangling references.

3. Missing Schema Definitions

Location: packages/server/src/api/rpc/openapi.ts:78, 82
Issue: createManyAndReturn and updateManyAndReturn operations reference ${modelName}CreateManyAndReturnArgs / ${modelName}UpdateManyAndReturnArgs but these schemas are never generated in generateSchemas().

4. Wrong Response Schema Types

Location: packages/server/src/api/rpc/openapi.ts:177
Issue: All operations use ${modelName}Response schema, but operations like findMany, count, aggregate return arrays/batch objects, not single models. This will cause client deserialization failures.


🟠 Major Logic Issues

1. Filter Kind Slicing Logic Bug

Location: packages/server/src/api/common/spec-utils.ts:29, 85

const modelSlicing = (slicing.models as Record<string, any>)[modelKey] ?? (slicing.models as any).$all;

Issue: Uses ?? operator which completely replaces $all rules when model-specific config exists. Global $all rules should be merged with model-specific overrides, not replaced.

Impact: Users lose global configuration when any model-specific settings are applied.

2. REST Generator Ignores Operation Filtering

Location: packages/server/src/api/rest/openapi.ts:91-94
Issue: Collection and single-resource paths are generated unconditionally, ignoring excludedOperations/includedOperations from queryOptions.slicing.

3. Inconsistent Field Slicing in Request Schemas

Location: packages/server/src/api/rest/openapi.ts:704, 769
Issue: buildCreateRequestSchema and buildUpdateRequestSchema check fieldDef.omit but never call isFieldOmitted(), so fields excluded via queryOptions still appear in request schemas.


🟡 Minor Issues

1. Missing Documentation

  • No JSDoc for complex filter kind logic in spec generators
  • Missing exports in main barrel module for concrete generator classes

2. Type Safety Improvements

  • Filter kind strings are magic strings - could benefit from const assertions
  • Some test files use any types that could be more specific

3. Performance Considerations

  • Repeated model lookups in OpenAPI generation could be cached
  • String operations in filter generation loops could benefit from memoization

🎯 Performance Assessment

Good:

  • Proper caching of serializers and model metadata
  • Efficient schema generation with reusable components

Areas for Optimization:

  • Model lookups are performed repeatedly during spec generation
  • Filter parameter generation could be memoized for repeated calls

🔒 Other Security Considerations

Input Validation:

  • ✅ Good Zod validation for handler options
  • ⚠️ queryOptions validation could be more specific (currently z.object().optional())

Type Safety:

  • ⚠️ Filter kind strings are magic strings - could benefit from const assertions
  • ✅ Good use of TypeScript for schema definitions

📋 Recommendations

Must Fix (Critical):

  1. Remove stack traces from error responses (security risk)
  2. Fix OpenAPI spec violations - wrong response references, missing schemas, broken refs
  3. Implement operation filtering in REST generator
  4. Fix filter kind slicing logic to properly merge $all with model-specific rules

Should Fix (Major):

  1. Apply isFieldOmitted() consistently in request schema builders
  2. Add missing response schema variants for different operation types
  3. Improve input validation specificity

Nice to Have:

  1. Add JSDoc documentation for complex logic
  2. Export concrete generator classes from main module
  3. Improve type safety with const assertions for filter kinds

Overall Assessment

This is a significant and valuable addition to the codebase with excellent architectural decisions. The filter kind slicing feature is well-designed and the test coverage is comprehensive. However, several critical issues must be addressed before merging:

  • Security: Stack trace exposure is a significant risk
  • Specification Compliance: Multiple OpenAPI violations will break client generation
  • Logic Correctness: Filter kind slicing and operation filtering bugs affect core functionality

Recommended Action: Address critical and major issues before merge. The foundation is solid but needs these important fixes.


…ons validation to OpenAPI generators

- REST OpenAPI generator now respects `slicing.excludedOperations`/`includedOperations` per model,
  mapping REST verbs to ORM operations (findMany, create, findUnique, update, delete)
- Add proper zod validation schema for `queryOptions` in both REST and RPC handlers
- Support `@@meta("description")` and `@meta("description")` for model/field schema descriptions
- Use named arg lookup in `getMetaDescription` for reliability

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@claude
Copy link
Contributor

claude bot commented Mar 19, 2026

Claude finished @ymc9's task —— View job


Code Review for OpenAPI Spec Generation PR

I've conducted a comprehensive review of this PR that adds OpenAPI spec generation with filter kind slicing support. Here's my detailed feedback:

Review Tasks:

  • Analyze architectural changes and new class structure
  • Review filter kind slicing implementation
  • Examine test coverage and quality
  • Check for potential bugs and edge cases
  • Review security considerations
  • Assess performance implications
  • Verify OpenAPI specification compliance

Major Strengths

Excellent Architecture & Design

  • Clean separation of concerns: Extracting OpenAPI spec generation into dedicated RestApiSpecGenerator and RPCApiSpecGenerator classes is a solid architectural decision
  • Shared utilities: The spec-utils.ts module provides excellent reusable functions for inclusion/exclusion logic
  • Interface-based design: OpenApiSpecGenerator interface enables extensibility and proper testing
  • Proper Zod validation: Good input validation for handler options

Robust Filter Kind Slicing Implementation

  • Well-designed inclusion/exclusion logic: isFilterKindIncluded function properly handles both includedFilterKinds and excludedFilterKinds with correct precedence
  • Proper fallback hierarchy: Model-specific → $all model → global defaults works as expected
  • Comprehensive filter coverage: Supports all four filter kinds (Equality, Like, Range, List) with appropriate type constraints

Strong Test Coverage

  • Comprehensive test suite: 4 test files with 5 baseline YAML fixtures
  • Migration validation: Proper migration from zenstack-v2 with baseline comparisons
  • Good test scenarios: Tests cover both REST and RPC approaches with various configurations

⚠️ Critical Security Issues

🔴 CRITICAL: Stack Trace Exposure

Location: packages/server/src/api/rest/index.ts:511

private handleGenericError(err: unknown): Response | PromiseLike<Response> {
    return this.makeError('unknownError', err instanceof Error ? `${err.message}\n${err.stack}` : 'Unknown error');
}

Risk: This exposes full stack traces in 500 responses, leaking internal server paths, code structure, and potentially sensitive information to clients.

Impact: High - Information disclosure vulnerability

Fix Required: Remove stack traces from responses, log them internally only.


🔴 Critical OpenAPI Specification Violations

1. Invalid Response References in RPC Generator

Location: packages/server/src/api/rpc/openapi.ts:181, 213, 245, 277, 289

'400': { $ref: '#/components/schemas/_ErrorResponse' }

Issue: Per OpenAPI 3.1 spec, response-level $ref must target components/responses, not components/schemas. Response Objects also require a description field.

2. Broken Self-References in WhereInput Schemas

Location: packages/server/src/api/rpc/openapi.ts:485-496

properties['AND'] = {
    oneOf: [
        { $ref: `#/components/schemas/_${_modelName}WhereInput` },  // ❌ Wrong name

Issue: References _${modelName}WhereInput but generateSchemas() creates ${modelName}WhereInput, causing dangling references.

3. Missing Schema Definitions

Location: packages/server/src/api/rpc/openapi.ts:78, 82
Issue: createManyAndReturn and updateManyAndReturn operations reference ${modelName}CreateManyAndReturnArgs / ${modelName}UpdateManyAndReturnArgs but these schemas are never generated in generateSchemas().

4. Wrong Response Schema Types

Location: packages/server/src/api/rpc/openapi.ts:177
Issue: All operations use ${modelName}Response schema, but operations like findMany, count, aggregate return arrays/batch objects, not single models. This will cause client deserialization failures.


🟠 Major Logic Issues

1. Filter Kind Slicing Logic Bug

Location: packages/server/src/api/common/spec-utils.ts:29, 85

const modelSlicing = (slicing.models as Record<string, any>)[modelKey] ?? (slicing.models as any).$all;

Issue: Uses ?? operator which completely replaces $all rules when model-specific config exists. Global $all rules should be merged with model-specific overrides, not replaced.

Impact: Users lose global configuration when any model-specific settings are applied.

2. REST Generator Ignores Operation Filtering

Location: packages/server/src/api/rest/openapi.ts:91-94
Issue: Collection and single-resource paths are generated unconditionally, ignoring excludedOperations/includedOperations from queryOptions.slicing.

3. Inconsistent Field Slicing in Request Schemas

Location: packages/server/src/api/rest/openapi.ts:704, 769
Issue: buildCreateRequestSchema and buildUpdateRequestSchema check fieldDef.omit but never call isFieldOmitted(), so fields excluded via queryOptions still appear in request schemas.


🟡 Minor Issues

1. Missing Documentation

  • No JSDoc for complex filter kind logic in spec generators
  • Missing exports in main barrel module for concrete generator classes

2. Type Safety Improvements

  • Filter kind strings are magic strings - could benefit from const assertions
  • Some test files use any types that could be more specific

3. Performance Considerations

  • Repeated model lookups in OpenAPI generation could be cached
  • String operations in filter generation loops could benefit from memoization

🎯 Performance Assessment

Good:

  • Proper caching of serializers and model metadata
  • Efficient schema generation with reusable components

Areas for Optimization:

  • Model lookups are performed repeatedly during spec generation
  • Filter parameter generation could be memoized for repeated calls

🔒 Other Security Considerations

Input Validation:

  • ✅ Good Zod validation for handler options
  • ⚠️ queryOptions validation could be more specific (currently z.object().optional())

Type Safety:

  • ⚠️ Filter kind strings are magic strings - could benefit from const assertions
  • ✅ Good use of TypeScript for schema definitions

📋 Recommendations

Must Fix (Critical):

  1. Remove stack traces from error responses (security risk)
  2. Fix OpenAPI spec violations - wrong response references, missing schemas, broken refs
  3. Implement operation filtering in REST generator
  4. Fix filter kind slicing logic to properly merge $all with model-specific rules

Should Fix (Major):

  1. Apply isFieldOmitted() consistently in request schema builders
  2. Add missing response schema variants for different operation types
  3. Improve input validation specificity

Nice to Have:

  1. Add JSDoc documentation for complex logic
  2. Export concrete generator classes from main module
  3. Improve type safety with const assertions for filter kinds

Overall Assessment

This is a significant and valuable addition to the codebase with excellent architectural decisions. The filter kind slicing feature is well-designed and the test coverage is comprehensive. However, several critical issues must be addressed before merging:

  • Security: Stack trace exposure is a significant risk
  • Specification Compliance: Multiple OpenAPI violations will break client generation
  • Logic Correctness: Filter kind slicing and operation filtering bugs affect core functionality

Recommended Action: Address critical and major issues before merge. The foundation is solid but needs these important fixes.


Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (7)
packages/server/src/api/rpc/openapi.ts (7)

487-501: ⚠️ Potential issue | 🔴 Critical

Fix the recursive WhereInput refs.

AND/OR/NOT point at #/components/schemas/_${_modelName}WhereInput, but only ${modelName}WhereInput is generated. Every recursive filter schema currently contains broken refs.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/server/src/api/rpc/openapi.ts` around lines 487 - 501, The recursive
filter refs for properties['AND'], properties['OR'], and properties['NOT']
currently reference `#/components/schemas/_`${_modelName}WhereInput which doesn't
exist; update those references to point to the actual generated schema name
(`#/components/schemas/`${_modelName}WhereInput without the extra leading
underscore or to `#/components/schemas/`${modelName}WhereInput depending on how
_modelName is defined) so the oneOf/array items use the real
${modelName}WhereInput schema; adjust the template where properties['AND'],
properties['OR'], and properties['NOT'] are built to use the correct
`${modelName}WhereInput` ref.

127-130: ⚠️ Potential issue | 🔴 Critical

Reference a Response Object here, not a Schema Object.

Each 400 entry is $ref-ing #/components/schemas/_ErrorResponse directly. OpenAPI only allows a Response Object at this level, so validators and generators will reject the document until you inline description/content or move the shared error payload under components.responses.

In OpenAPI 3.1, can a response entry under `responses` directly use `{ "$ref": "#/components/schemas/Error" }`, or must it reference `#/components/responses/...` or inline a Response Object with `content.schema.$ref`?

Also applies to: 181-181, 213-213, 245-245, 277-277, 289-289

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/server/src/api/rpc/openapi.ts` around lines 127 - 130, The OpenAPI
responses are incorrectly $ref-ing a Schema Object
(`#/components/schemas/_ErrorResponse`) instead of a Response Object; update each
offending responses entry (the '400' response blocks at the shown spots) to
either reference a Response Object under components.responses (e.g. add a
components.responses.BadRequest that sets description and content.schema.$ref:
'#/components/schemas/_ErrorResponse' and change the response to $ref:
'#/components/responses/BadRequest') or inline a proper Response Object for
'400' with description and content: {'application/json': { schema: { $ref:
'#/components/schemas/_ErrorResponse' } } }; apply this change to the other
listed occurrences as well.

172-177: ⚠️ Potential issue | 🟠 Major

Use per-operation response components.

These builders always emit ${modelName}Response, but findMany, count, aggregate, groupBy, exists, createMany*, updateMany*, and deleteMany do not return a single-model payload. The generated contract will describe the wrong wire format for most RPC operations.

Also applies to: 204-209, 237-242, 269-273

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/server/src/api/rpc/openapi.ts` around lines 172 - 177, The response
schema always uses `${modelName}Response`, which is incorrect for many RPC ops;
update the response generation in openapi.ts to choose operation-specific
components instead of a single `${modelName}Response`. Implement a small helper
(e.g. getResponseComponentForOp(op, modelName)) or switch on the `op` variable
to return the correct schema ref for operations like findMany (e.g.
`${modelName}FindManyResponse`), count (`${modelName}CountResponse`), aggregate,
groupBy, exists, createMany, updateMany, deleteMany, etc., and replace the
current `{ $ref: \`#/components/schemas/${modelName}Response\` }` usage with
that helper so each RPC operation emits the correct response component.

78-82: ⚠️ Potential issue | 🔴 Critical

Generate *ManyAndReturnArgs components before referencing them.

createManyAndReturn and updateManyAndReturn are exposed as endpoints here, but generateSchemas() never registers ${modelName}CreateManyAndReturnArgs or ${modelName}UpdateManyAndReturnArgs. Both request-body refs are dangling right now.

Also applies to: 350-354

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/server/src/api/rpc/openapi.ts` around lines 78 - 82,
generateSchemas() currently doesn't register
CreateManyAndReturn/UpdateManyAndReturn argument schemas yet the OpenAPI
operations list in openapi.ts exposes createManyAndReturn and
updateManyAndReturn endpoints, leaving ${modelName}CreateManyAndReturnArgs and
${modelName}UpdateManyAndReturnArgs refs dangling; update the schema generation
logic (the function generateSchemas and/or wherever model argument schemas are
built) to produce and register `${modelName}CreateManyAndReturnArgs` and
`${modelName}UpdateManyAndReturnArgs` for each model (mirroring the pattern used
for existing CreateManyArgs/UpdateManyArgs), ensuring the registered component
names match the request-body $ref used by the operations in openapi.ts so the
refs are resolved.

381-391: ⚠️ Potential issue | 🟠 Major

Don't mark relation fields as required in the shared model schema.

RPC results only include relations when the caller selects/includes them. Pushing every non-optional relation into required makes ordinary responses invalid unless those relations were explicitly requested.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/server/src/api/rpc/openapi.ts` around lines 381 - 391, The loop is
currently adding every non-optional, non-array field to the required array,
which incorrectly includes relation fields; change the required push to skip
relations by extending the condition that adds required entries—i.e., only push
fieldName when (!fieldDef.optional && !fieldDef.array && !isRelation(fieldDef)).
Implement isRelation detection based on the model metadata used in this codebase
(e.g. check for fieldDef.relation, fieldDef.relatedModel, or fieldDef.kind ===
'relation' as appropriate) so relation fields from modelDef.fields are not
marked required; keep the existing calls to fieldToSchema and isFieldOmitted
unchanged.

698-745: ⚠️ Potential issue | 🟠 Major

Make filter values follow the field shape, not just the element type.

Starting from typeToSchema(fieldDef.type) strips nullability and array wrappers, so equals/not/shorthand filters cannot represent null comparisons or whole-list equality correctly. Keep the element schema for list operators like has, but wrap the equality value schema to match the field itself.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/server/src/api/rpc/openapi.ts` around lines 698 - 745,
buildFieldFilterSchema currently uses baseSchema = typeToSchema(fieldDef.type)
which strips nullability/array wrappers, so equality filters
(equals/not/in/notIn and the oneOf shorthand) cannot represent null or
whole-list equality; compute a fullFieldSchema that preserves the field shape
(nullability and array wrappers) from fieldDef (e.g., a new schema result from a
variant of typeToSchema that does not unwrap arrays/nulls or reconstruct it from
fieldDef), then use fullFieldSchema for equals, not, in.items, notIn.items and
for the oneOf shorthand; keep using baseSchema (element schema) for
list-specific operators like has/hasEvery/hasSome/isEmpty. Ensure references to
buildFieldFilterSchema, typeToSchema, fieldDef, equals, not, in, notIn, has,
hasEvery, hasSome, and oneOf are updated accordingly.

419-421: ⚠️ Potential issue | 🟠 Major

Preserve nullable/list wrappers in create/update inputs.

typeToSchema(fieldDef.type) only returns the scalar/entity schema, so nullable scalars become non-null, scalar-list fields become scalars, and required list fields never enter required. Reuse fieldToSchema() or an equivalent wrapper helper here.

Also applies to: 441-441

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/server/src/api/rpc/openapi.ts` around lines 419 - 421, The
create/update input generation uses typeToSchema(fieldDef.type) which strips
nullable and array wrappers; replace those calls with fieldToSchema(fieldDef)
(or a small helper that calls fieldToSchema) so the returned schema preserves
nullable and list wrappers and the required array logic remains correct (so
scalar-list types and nullable fields are represented properly and required only
applies when appropriate); update both places where properties[fieldName] =
this.typeToSchema(fieldDef.type) is used (including the other occurrence noted)
to use the wrapper that accepts the full fieldDef.
🧹 Nitpick comments (1)
packages/server/test/openapi/rpc-openapi.test.ts (1)

16-24: Add a local $ref smoke test.

These assertions sample a few known paths/components, but they never verify that every #/components/schemas/* ref resolves. A tiny walker here would catch invalid-spec regressions like missing *ManyAndReturnArgs components or the underscored WhereInput refs immediately.

Also applies to: 74-83

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/server/test/openapi/rpc-openapi.test.ts` around lines 16 - 24, Add a
small $ref resolution smoke test that walks every schema under
spec.components.schemas and asserts each local ref "#/components/schemas/..."
actually resolves; implement a helper (e.g., resolveLocalRef or
walkAndResolveRefs) that traverses objects, finds "$ref" strings, and for each
local ref verifies spec.components.schemas[refName] is defined, then call it
from the existing "document structure is valid" test (and the similar test
around the 74-83 block) to fail if any component ref like "*ManyAndReturnArgs"
or underscored "WhereInput" is missing.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/server/src/api/common/schemas.ts`:
- Around line 5-27: The current validation in queryOptionsSchema (and nested
slicingSchema, modelSlicingSchema, fieldSlicingSchema) is too permissive: it
uses z.looseObject and free-form z.string().array() for operations and filter
kinds, allowing typos to pass; change z.looseObject(...) to strict z.object(...)
for queryOptionsSchema, slicingSchema, modelSlicingSchema, and
fieldSlicingSchema to reject unknown keys, and replace the free-form arrays
(e.g., includedOperations, excludedOperations, includedFilterKinds,
excludedFilterKinds) with z.enum(...) arrays that list the allowed operation
names (e.g., 'findMany', 'findUnique', 'create', 'update', 'delete', etc.) and
allowed filter kinds (e.g., 'Equality', 'Range', 'Like', 'List') so invalid
values fail validation up-front; keep the same optional() modifiers and record
shapes for models/fields as before but validate enum membership for those
arrays.

---

Duplicate comments:
In `@packages/server/src/api/rpc/openapi.ts`:
- Around line 487-501: The recursive filter refs for properties['AND'],
properties['OR'], and properties['NOT'] currently reference
`#/components/schemas/_`${_modelName}WhereInput which doesn't exist; update those
references to point to the actual generated schema name
(`#/components/schemas/`${_modelName}WhereInput without the extra leading
underscore or to `#/components/schemas/`${modelName}WhereInput depending on how
_modelName is defined) so the oneOf/array items use the real
${modelName}WhereInput schema; adjust the template where properties['AND'],
properties['OR'], and properties['NOT'] are built to use the correct
`${modelName}WhereInput` ref.
- Around line 127-130: The OpenAPI responses are incorrectly $ref-ing a Schema
Object (`#/components/schemas/_ErrorResponse`) instead of a Response Object;
update each offending responses entry (the '400' response blocks at the shown
spots) to either reference a Response Object under components.responses (e.g.
add a components.responses.BadRequest that sets description and
content.schema.$ref: '#/components/schemas/_ErrorResponse' and change the
response to $ref: '#/components/responses/BadRequest') or inline a proper
Response Object for '400' with description and content: {'application/json': {
schema: { $ref: '#/components/schemas/_ErrorResponse' } } }; apply this change
to the other listed occurrences as well.
- Around line 172-177: The response schema always uses `${modelName}Response`,
which is incorrect for many RPC ops; update the response generation in
openapi.ts to choose operation-specific components instead of a single
`${modelName}Response`. Implement a small helper (e.g.
getResponseComponentForOp(op, modelName)) or switch on the `op` variable to
return the correct schema ref for operations like findMany (e.g.
`${modelName}FindManyResponse`), count (`${modelName}CountResponse`), aggregate,
groupBy, exists, createMany, updateMany, deleteMany, etc., and replace the
current `{ $ref: \`#/components/schemas/${modelName}Response\` }` usage with
that helper so each RPC operation emits the correct response component.
- Around line 78-82: generateSchemas() currently doesn't register
CreateManyAndReturn/UpdateManyAndReturn argument schemas yet the OpenAPI
operations list in openapi.ts exposes createManyAndReturn and
updateManyAndReturn endpoints, leaving ${modelName}CreateManyAndReturnArgs and
${modelName}UpdateManyAndReturnArgs refs dangling; update the schema generation
logic (the function generateSchemas and/or wherever model argument schemas are
built) to produce and register `${modelName}CreateManyAndReturnArgs` and
`${modelName}UpdateManyAndReturnArgs` for each model (mirroring the pattern used
for existing CreateManyArgs/UpdateManyArgs), ensuring the registered component
names match the request-body $ref used by the operations in openapi.ts so the
refs are resolved.
- Around line 381-391: The loop is currently adding every non-optional,
non-array field to the required array, which incorrectly includes relation
fields; change the required push to skip relations by extending the condition
that adds required entries—i.e., only push fieldName when (!fieldDef.optional &&
!fieldDef.array && !isRelation(fieldDef)). Implement isRelation detection based
on the model metadata used in this codebase (e.g. check for fieldDef.relation,
fieldDef.relatedModel, or fieldDef.kind === 'relation' as appropriate) so
relation fields from modelDef.fields are not marked required; keep the existing
calls to fieldToSchema and isFieldOmitted unchanged.
- Around line 698-745: buildFieldFilterSchema currently uses baseSchema =
typeToSchema(fieldDef.type) which strips nullability/array wrappers, so equality
filters (equals/not/in/notIn and the oneOf shorthand) cannot represent null or
whole-list equality; compute a fullFieldSchema that preserves the field shape
(nullability and array wrappers) from fieldDef (e.g., a new schema result from a
variant of typeToSchema that does not unwrap arrays/nulls or reconstruct it from
fieldDef), then use fullFieldSchema for equals, not, in.items, notIn.items and
for the oneOf shorthand; keep using baseSchema (element schema) for
list-specific operators like has/hasEvery/hasSome/isEmpty. Ensure references to
buildFieldFilterSchema, typeToSchema, fieldDef, equals, not, in, notIn, has,
hasEvery, hasSome, and oneOf are updated accordingly.
- Around line 419-421: The create/update input generation uses
typeToSchema(fieldDef.type) which strips nullable and array wrappers; replace
those calls with fieldToSchema(fieldDef) (or a small helper that calls
fieldToSchema) so the returned schema preserves nullable and list wrappers and
the required array logic remains correct (so scalar-list types and nullable
fields are represented properly and required only applies when appropriate);
update both places where properties[fieldName] =
this.typeToSchema(fieldDef.type) is used (including the other occurrence noted)
to use the wrapper that accepts the full fieldDef.

---

Nitpick comments:
In `@packages/server/test/openapi/rpc-openapi.test.ts`:
- Around line 16-24: Add a small $ref resolution smoke test that walks every
schema under spec.components.schemas and asserts each local ref
"#/components/schemas/..." actually resolves; implement a helper (e.g.,
resolveLocalRef or walkAndResolveRefs) that traverses objects, finds "$ref"
strings, and for each local ref verifies spec.components.schemas[refName] is
defined, then call it from the existing "document structure is valid" test (and
the similar test around the 74-83 block) to fail if any component ref like
"*ManyAndReturnArgs" or underscored "WhereInput" is missing.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: b5b09c66-02ff-48ee-8c80-d8558f369535

📥 Commits

Reviewing files that changed from the base of the PR and between 1853a04 and 76b634c.

📒 Files selected for processing (8)
  • packages/server/src/api/common/schemas.ts
  • packages/server/src/api/common/spec-utils.ts
  • packages/server/src/api/rest/index.ts
  • packages/server/src/api/rest/openapi.ts
  • packages/server/src/api/rpc/index.ts
  • packages/server/src/api/rpc/openapi.ts
  • packages/server/test/openapi/rest-openapi.test.ts
  • packages/server/test/openapi/rpc-openapi.test.ts
✅ Files skipped from review due to trivial changes (2)
  • packages/server/test/openapi/rest-openapi.test.ts
  • packages/server/src/api/rest/openapi.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/server/src/api/rpc/index.ts
  • packages/server/src/api/common/spec-utils.ts

…nd remove unnecessary `as any` casts

- Skip relation paths, read schema fields, and create/update relationship
  entries when the related model is excluded by slicing
- Replace `as any` with `as OpenAPIV3_1.PathItemObject` where possible and
  remove unnecessary cast on `generateSharedParams()`

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@claude
Copy link
Contributor

claude bot commented Mar 19, 2026

Claude finished @ymc9's task —— View job


Code Review for OpenAPI Spec Generation PR

I've conducted a comprehensive review of this PR that adds OpenAPI spec generation with filter kind slicing support. Here's my detailed feedback:

Review Tasks:

  • Analyze architectural changes and new class structure
  • Review filter kind slicing implementation
  • Examine test coverage and quality
  • Check for potential bugs and edge cases
  • Review security considerations
  • Assess performance implications
  • Verify OpenAPI specification compliance

Major Strengths

Excellent Architecture & Design

  • Clean separation of concerns: Extracting OpenAPI spec generation into dedicated RestApiSpecGenerator and RPCApiSpecGenerator classes is a solid architectural decision
  • Shared utilities: The spec-utils.ts module provides excellent reusable functions for inclusion/exclusion logic
  • Interface-based design: OpenApiSpecGenerator interface enables extensibility and proper testing
  • Proper Zod validation: Good input validation for handler options

Robust Filter Kind Slicing Implementation

  • Well-designed inclusion/exclusion logic: isFilterKindIncluded function properly handles both includedFilterKinds and excludedFilterKinds with correct precedence
  • Proper fallback hierarchy: Model-specific → $all model → global defaults works as expected
  • Comprehensive filter coverage: Supports all four filter kinds (Equality, Like, Range, List) with appropriate type constraints

Strong Test Coverage

  • Comprehensive test suite: 4 test files with 5 baseline YAML fixtures
  • Migration validation: Proper migration from zenstack-v2 with baseline comparisons
  • Good test scenarios: Tests cover both REST and RPC approaches with various configurations

⚠️ Critical Security Issues

🔴 CRITICAL: Stack Trace Exposure

Location: packages/server/src/api/rest/index.ts:511

private handleGenericError(err: unknown): Response | PromiseLike<Response> {
    return this.makeError('unknownError', err instanceof Error ? `${err.message}\n${err.stack}` : 'Unknown error');
}

Risk: This exposes full stack traces in 500 responses, leaking internal server paths, code structure, and potentially sensitive information to clients.

Impact: High - Information disclosure vulnerability

Fix Required: Remove stack traces from responses, log them internally only.


🔴 Critical OpenAPI Specification Violations

1. Invalid Response References in RPC Generator

Location: packages/server/src/api/rpc/openapi.ts:181, 213, 245, 277, 289

'400': { $ref: '#/components/schemas/_ErrorResponse' }

Issue: Per OpenAPI 3.1 spec, response-level $ref must target components/responses, not components/schemas. Response Objects also require a description field.

2. Broken Self-References in WhereInput Schemas

Location: packages/server/src/api/rpc/openapi.ts:485-496

properties['AND'] = {
    oneOf: [
        { $ref: `#/components/schemas/_${_modelName}WhereInput` },  // ❌ Wrong name

Issue: References _${modelName}WhereInput but generateSchemas() creates ${modelName}WhereInput, causing dangling references.

3. Missing Schema Definitions

Location: packages/server/src/api/rpc/openapi.ts:78, 82
Issue: createManyAndReturn and updateManyAndReturn operations reference ${modelName}CreateManyAndReturnArgs / ${modelName}UpdateManyAndReturnArgs but these schemas are never generated in generateSchemas().

4. Wrong Response Schema Types

Location: packages/server/src/api/rpc/openapi.ts:177
Issue: All operations use ${modelName}Response schema, but operations like findMany, count, aggregate return arrays/batch objects, not single models. This will cause client deserialization failures.


🟠 Major Logic Issues

1. Filter Kind Slicing Logic Bug

Location: packages/server/src/api/common/spec-utils.ts:29, 85

const modelSlicing = (slicing.models as Record<string, any>)[modelKey] ?? (slicing.models as any).$all;

Issue: Uses ?? operator which completely replaces $all rules when model-specific config exists. Global $all rules should be merged with model-specific overrides, not replaced.

Impact: Users lose global configuration when any model-specific settings are applied.

2. REST Generator Ignores Operation Filtering

Location: packages/server/src/api/rest/openapi.ts:91-94
Issue: Collection and single-resource paths are generated unconditionally, ignoring excludedOperations/includedOperations from queryOptions.slicing.

3. Inconsistent Field Slicing in Request Schemas

Location: packages/server/src/api/rest/openapi.ts:704, 769
Issue: buildCreateRequestSchema and buildUpdateRequestSchema check fieldDef.omit but never call isFieldOmitted(), so fields excluded via queryOptions still appear in request schemas.


🟡 Minor Issues

1. Missing Documentation

  • No JSDoc for complex filter kind logic in spec generators
  • Missing exports in main barrel module for concrete generator classes

2. Type Safety Improvements

  • Filter kind strings are magic strings - could benefit from const assertions
  • Some test files use any types that could be more specific

3. Performance Considerations

  • Repeated model lookups in OpenAPI generation could be cached
  • String operations in filter generation loops could benefit from memoization

🎯 Performance Assessment

Good:

  • Proper caching of serializers and model metadata
  • Efficient schema generation with reusable components

Areas for Optimization:

  • Model lookups are performed repeatedly during spec generation
  • Filter parameter generation could be memoized for repeated calls

🔒 Other Security Considerations

Input Validation:

  • ✅ Good Zod validation for handler options
  • ⚠️ queryOptions validation could be more specific (currently z.object().optional())

Type Safety:

  • ⚠️ Filter kind strings are magic strings - could benefit from const assertions
  • ✅ Good use of TypeScript for schema definitions

📋 Recommendations

Must Fix (Critical):

  1. Remove stack traces from error responses (security risk)
  2. Fix OpenAPI spec violations - wrong response references, missing schemas, broken refs
  3. Implement operation filtering in REST generator
  4. Fix filter kind slicing logic to properly merge $all with model-specific rules

Should Fix (Major):

  1. Apply isFieldOmitted() consistently in request schema builders
  2. Add missing response schema variants for different operation types
  3. Improve input validation specificity

Nice to Have:

  1. Add JSDoc documentation for complex logic
  2. Export concrete generator classes from main module
  3. Improve type safety with const assertions for filter kinds

Overall Assessment

This is a significant and valuable addition to the codebase with excellent architectural decisions. The filter kind slicing feature is well-designed and the test coverage is comprehensive. However, several critical issues must be addressed before merging:

  • Security: Stack trace exposure is a significant risk
  • Specification Compliance: Multiple OpenAPI violations will break client generation
  • Logic Correctness: Filter kind slicing and operation filtering bugs affect core functionality

Recommended Action: Address critical and major issues before merge. The foundation is solid but needs these important fixes.


…and add fetch-related response schemas

Error responses were using `$ref` to schemas directly in response positions, which is invalid per OpenAPI spec. Replace with inline Response Objects containing description and content. Also add proper response schema references to fetch-related endpoints.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@claude
Copy link
Contributor

claude bot commented Mar 19, 2026

Claude finished @ymc9's task —— View job


Code Review for OpenAPI Spec Generation PR

I've conducted a comprehensive review of this PR that adds OpenAPI spec generation with filter kind slicing support. Here's my detailed feedback:

Review Tasks:

  • Read repository guidelines and understand codebase
  • Analyze architectural changes and new class structure
  • Review filter kind slicing implementation
  • Examine test coverage and quality
  • Check for potential bugs and edge cases
  • Review security considerations
  • Assess performance implications
  • Verify OpenAPI specification compliance

Major Strengths

Excellent Architecture & Design

  • Clean separation of concerns: Extracting OpenAPI spec generation into dedicated RestApiSpecGenerator and RPCApiSpecGenerator classes is a solid architectural decision
  • Shared utilities: The spec-utils.ts module provides excellent reusable functions for inclusion/exclusion logic
  • Interface-based design: OpenApiSpecGenerator interface enables extensibility and proper testing
  • Proper Zod validation: Good input validation for handler options

Robust Filter Kind Slicing Implementation

  • Well-designed inclusion/exclusion logic: isFilterKindIncluded function properly handles both includedFilterKinds and excludedFilterKinds with correct precedence
  • Proper fallback hierarchy: Model-specific → $all model → global defaults works as expected
  • Comprehensive filter coverage: Supports all four filter kinds (Equality, Like, Range, List) with appropriate type constraints

Strong Test Coverage

  • Comprehensive test suite: 4 test files with 5 baseline YAML fixtures
  • Migration validation: Proper migration from zenstack-v2 with baseline comparisons
  • Good test scenarios: Tests cover both REST and RPC approaches with various configurations

⚠️ Critical Security Issues

🔴 CRITICAL: Stack Trace Exposure

Location: packages/server/src/api/rest/index.ts:511

private handleGenericError(err: unknown): Response | PromiseLike<Response> {
    return this.makeError('unknownError', err instanceof Error ? `${err.message}\n${err.stack}` : 'Unknown error');
}

Risk: This exposes full stack traces in 500 responses, leaking internal server paths, code structure, and potentially sensitive information to clients.

Impact: High - Information disclosure vulnerability

Fix Required: Remove stack traces from responses, log them internally only.


🔴 Critical OpenAPI Specification Violations

1. Invalid Response References in RPC Generator

Location: packages/server/src/api/rpc/openapi.ts:181, 213, 245, 277, 289

'400': { $ref: '#/components/schemas/_ErrorResponse' }

Issue: Per OpenAPI 3.1 spec, response-level $ref must target components/responses, not components/schemas. Response Objects also require a description field.

2. Broken Self-References in WhereInput Schemas

Location: packages/server/src/api/rpc/openapi.ts:485-496

properties['AND'] = {
    oneOf: [
        { $ref: `#/components/schemas/_${_modelName}WhereInput` },  // ❌ Wrong name

Issue: References _${modelName}WhereInput but generateSchemas() creates ${modelName}WhereInput, causing dangling references.

3. Missing Schema Definitions

Location: packages/server/src/api/rpc/openapi.ts:78, 82
Issue: createManyAndReturn and updateManyAndReturn operations reference ${modelName}CreateManyAndReturnArgs / ${modelName}UpdateManyAndReturnArgs but these schemas are never generated in generateSchemas().

4. Wrong Response Schema Types

Location: packages/server/src/api/rpc/openapi.ts:177
Issue: All operations use ${modelName}Response schema, but operations like findMany, count, aggregate return arrays/batch objects, not single models. This will cause client deserialization failures.


🟠 Major Logic Issues

1. Filter Kind Slicing Logic Bug

Location: packages/server/src/api/common/spec-utils.ts:29, 85

const modelSlicing = (slicing.models as Record<string, any>)[modelKey] ?? (slicing.models as any).$all;

Issue: Uses ?? operator which completely replaces $all rules when model-specific config exists. Global $all rules should be merged with model-specific overrides, not replaced.

Impact: Users lose global configuration when any model-specific settings are applied.

2. REST Generator Ignores Operation Filtering

Location: packages/server/src/api/rest/openapi.ts:91-94
Issue: Collection and single-resource paths are generated unconditionally, ignoring excludedOperations/includedOperations from queryOptions.slicing.

3. Inconsistent Field Slicing in Request Schemas

Location: packages/server/src/api/rest/openapi.ts:704, 769
Issue: buildCreateRequestSchema and buildUpdateRequestSchema check fieldDef.omit but never call isFieldOmitted(), so fields excluded via queryOptions still appear in request schemas.


🟡 Minor Issues

1. Missing Documentation

  • No JSDoc for complex filter kind logic in spec generators
  • Missing exports in main barrel module for concrete generator classes

2. Type Safety Improvements

  • Filter kind strings are magic strings - could benefit from const assertions
  • Some test files use any types that could be more specific

3. Performance Considerations

  • Repeated model lookups in OpenAPI generation could be cached
  • String operations in filter generation loops could benefit from memoization

🎯 Performance Assessment

Good:

  • Proper caching of serializers and model metadata
  • Efficient schema generation with reusable components

Areas for Optimization:

  • Model lookups are performed repeatedly during spec generation
  • Filter parameter generation could be memoized for repeated calls

🔒 Other Security Considerations

Input Validation:

  • ✅ Good Zod validation for handler options
  • ⚠️ queryOptions validation could be more specific (currently z.object().optional())

Type Safety:

  • ⚠️ Filter kind strings are magic strings - could benefit from const assertions
  • ✅ Good use of TypeScript for schema definitions

📋 Recommendations

Must Fix (Critical):

  1. Remove stack traces from error responses (security risk)
  2. Fix OpenAPI spec violations - wrong response references, missing schemas, broken refs
  3. Implement operation filtering in REST generator
  4. Fix filter kind slicing logic to properly merge $all with model-specific rules

Should Fix (Major):

  1. Apply isFieldOmitted() consistently in request schema builders
  2. Add missing response schema variants for different operation types
  3. Improve input validation specificity

Nice to Have:

  1. Add JSDoc documentation for complex logic
  2. Export concrete generator classes from main module
  3. Improve type safety with const assertions for filter kinds

Overall Assessment

This is a significant and valuable addition to the codebase with excellent architectural decisions. The filter kind slicing feature is well-designed and the test coverage is comprehensive. However, several critical issues must be addressed before merging:

  • Security: Stack trace exposure is a significant risk
  • Specification Compliance: Multiple OpenAPI violations will break client generation
  • Logic Correctness: Filter kind slicing and operation filtering bugs affect core functionality

Recommended Action: Address critical and major issues before merge. The foundation is solid but needs these important fixes.


@claude
Copy link
Contributor

claude bot commented Mar 19, 2026

Claude finished @ymc9's task —— View job


Code Review for OpenAPI Spec Generation PR

I've conducted a comprehensive review of this PR that adds OpenAPI spec generation with filter kind slicing support. Here's my detailed feedback:

Review Tasks:

  • Read repository guidelines and understand codebase
  • Analyze architectural changes and new class structure
  • Review filter kind slicing implementation
  • Examine test coverage and quality
  • Check for potential bugs and edge cases
  • Review security considerations
  • Assess performance implications
  • Verify OpenAPI specification compliance

Major Strengths

Excellent Architecture & Design

  • Clean separation of concerns: Extracting OpenAPI spec generation into dedicated RestApiSpecGenerator and RPCApiSpecGenerator classes is a solid architectural decision
  • Shared utilities: The spec-utils.ts module provides excellent reusable functions for inclusion/exclusion logic
  • Interface-based design: OpenApiSpecGenerator interface enables extensibility and proper testing
  • Proper Zod validation: Good input validation for handler options

Robust Filter Kind Slicing Implementation

  • Well-designed inclusion/exclusion logic: isFilterKindIncluded function properly handles both includedFilterKinds and excludedFilterKinds with correct precedence
  • Proper fallback hierarchy: Model-specific → $all model → global defaults works as expected
  • Comprehensive filter coverage: Supports all four filter kinds (Equality, Like, Range, List) with appropriate type constraints

Strong Test Coverage

  • Comprehensive test suite: 4 test files with 5 baseline YAML fixtures
  • Migration validation: Proper migration from zenstack-v2 with baseline comparisons
  • Good test scenarios: Tests cover both REST and RPC approaches with various configurations

⚠️ Critical Security Issues

🔴 CRITICAL: Stack Trace Exposure

Location: packages/server/src/api/rest/index.ts:511

private handleGenericError(err: unknown): Response | PromiseLike<Response> {
    return this.makeError('unknownError', err instanceof Error ? `${err.message}\n${err.stack}` : 'Unknown error');
}

Risk: This exposes full stack traces in 500 responses, leaking internal server paths, code structure, and potentially sensitive information to clients.

Impact: High - Information disclosure vulnerability

Fix Required: Remove stack traces from responses, log them internally only.


🔴 Critical OpenAPI Specification Violations

1. Broken Self-References in WhereInput Schemas

Location: packages/server/src/api/rpc/openapi.ts:558-569

properties['AND'] = {
    oneOf: [
        { $ref: `#/components/schemas/_${_modelName}WhereInput` },  // ❌ Wrong name

Issue: References _${modelName}WhereInput but generateSchemas() creates ${modelName}WhereInput, causing dangling references.

2. Wrong Response Schema Types

Location: packages/server/src/api/rpc/openapi.ts:193
Issue: All operations use ${modelName}Response schema, but operations like findMany, count, aggregate return arrays/batch objects, not single models. This will cause client deserialization failures.


🟠 Major Logic Issues

1. Filter Kind Slicing Logic Bug

Location: packages/server/src/api/common/spec-utils.ts:29, 85

const modelSlicing = (slicing.models as Record<string, any>)[modelKey] ?? (slicing.models as any).$all;

Issue: Uses ?? operator which completely replaces $all rules when model-specific config exists. Global $all rules should be merged with model-specific overrides, not replaced.

Impact: Users lose global configuration when any model-specific settings are applied.

2. REST Generator Ignores Operation Filtering

Location: packages/server/src/api/rest/openapi.ts:102-110
Issue: Collection and single-resource paths are generated unconditionally, ignoring excludedOperations/includedOperations from queryOptions.slicing.

3. Inconsistent Field Slicing in Request Schemas

Location: packages/server/src/api/rest/openapi.ts:694-757, 759-814
Issue: buildCreateRequestSchema and buildUpdateRequestSchema check fieldDef.omit but never call isFieldOmitted(), so fields excluded via queryOptions still appear in request schemas.


🟡 Minor Issues

1. Missing Documentation

  • No JSDoc for complex filter kind logic in spec generators
  • Missing exports in main barrel module for concrete generator classes

2. Type Safety Improvements

  • Filter kind strings are magic strings - could benefit from const assertions
  • Some test files use any types that could be more specific

3. Performance Considerations

  • Repeated model lookups in OpenAPI generation could be cached
  • String operations in filter generation loops could benefit from memoization

🎯 Performance Assessment

Good:

  • Proper caching of serializers and model metadata
  • Efficient schema generation with reusable components

Areas for Optimization:

  • Model lookups are performed repeatedly during spec generation
  • Filter parameter generation could be memoized for repeated calls

🔒 Other Security Considerations

Input Validation:

  • ✅ Good Zod validation for handler options
  • ⚠️ queryOptions validation could be more specific (currently z.object().optional())

Type Safety:

  • ⚠️ Filter kind strings are magic strings - could benefit from const assertions
  • ✅ Good use of TypeScript for schema definitions

📋 Recommendations

Must Fix (Critical):

  1. Remove stack traces from error responses (security risk)
  2. Fix OpenAPI spec violations - wrong response references, missing schemas, broken refs
  3. Implement operation filtering in REST generator
  4. Fix filter kind slicing logic to properly merge $all with model-specific rules

Should Fix (Major):

  1. Apply isFieldOmitted() consistently in request schema builders
  2. Add missing response schema variants for different operation types
  3. Improve input validation specificity

Nice to Have:

  1. Add JSDoc documentation for complex logic
  2. Export concrete generator classes from main module
  3. Improve type safety with const assertions for filter kinds

Overall Assessment

This is a significant and valuable addition to the codebase with excellent architectural decisions. The filter kind slicing feature is well-designed and the test coverage is comprehensive. However, several critical issues must be addressed before merging:

  • Security: Stack trace exposure is a significant risk
  • Specification Compliance: Multiple OpenAPI violations will break client generation
  • Logic Correctness: Filter kind slicing and operation filtering bugs affect core functionality

Recommended Action: Address critical and major issues before merge. The foundation is solid but needs these important fixes.


@claude
Copy link
Contributor

claude bot commented Mar 19, 2026

Claude finished @ymc9's task —— View job


Code Review for OpenAPI Spec Generation PR

I've conducted a comprehensive review of this PR that adds OpenAPI spec generation with filter kind slicing support. Here's my detailed feedback:

Review Tasks:

  • Read repository guidelines and understand codebase
  • Analyze architectural changes and new class structure
  • Review filter kind slicing implementation
  • Examine test coverage and quality
  • Check for potential bugs and edge cases
  • Review security considerations
  • Assess performance implications
  • Verify OpenAPI specification compliance

Major Strengths

Excellent Architecture & Design

  • Clean separation of concerns: Extracting OpenAPI spec generation into dedicated RestApiSpecGenerator and RPCApiSpecGenerator classes is a solid architectural decision
  • Shared utilities: The spec-utils.ts module provides excellent reusable functions for inclusion/exclusion logic
  • Interface-based design: OpenApiSpecGenerator interface enables extensibility and proper testing
  • Proper Zod validation: Good input validation for handler options

Robust Filter Kind Slicing Implementation

  • Well-designed inclusion/exclusion logic: isFilterKindIncluded function properly handles both includedFilterKinds and excludedFilterKinds with correct precedence
  • Proper fallback hierarchy: Model-specific → $all model → global defaults works as expected
  • Comprehensive filter coverage: Supports all four filter kinds (Equality, Like, Range, List) with appropriate type constraints

Strong Test Coverage

  • Comprehensive test suite: 4 test files with 5 baseline YAML fixtures
  • Migration validation: Proper migration from zenstack-v2 with baseline comparisons
  • Good test scenarios: Tests cover both REST and RPC approaches with various configurations

⚠️ Critical Security Issues

🔴 CRITICAL: Stack Trace Exposure

Location: packages/server/src/api/rest/index.ts:511

private handleGenericError(err: unknown): Response | PromiseLike<Response> {
    return this.makeError('unknownError', err instanceof Error ? `${err.message}\n${err.stack}` : 'Unknown error');
}

Risk: This exposes full stack traces in 500 responses, leaking internal server paths, code structure, and potentially sensitive information to clients.

Impact: High - Information disclosure vulnerability

Fix Required: Remove stack traces from responses, log them internally only.


🔴 Critical OpenAPI Specification Violations

1. Broken Self-References in WhereInput Schemas

Location: packages/server/src/api/rpc/openapi.ts:558-569

properties['AND'] = {
    oneOf: [
        { $ref: `#/components/schemas/_${_modelName}WhereInput` },  // ❌ Wrong name

Issue: References _${modelName}WhereInput but generateSchemas() creates ${modelName}WhereInput, causing dangling references.

2. Wrong Response Schema Types

Location: packages/server/src/api/rpc/openapi.ts:193
Issue: All operations use ${modelName}Response schema, but operations like findMany, count, aggregate return arrays/batch objects, not single models. This will cause client deserialization failures.


🟠 Major Logic Issues

1. Filter Kind Slicing Logic Bug

Location: packages/server/src/api/common/spec-utils.ts:29, 85

const modelSlicing = (slicing.models as Record<string, any>)[modelKey] ?? (slicing.models as any).$all;

Issue: Uses ?? operator which completely replaces $all rules when model-specific config exists. Global $all rules should be merged with model-specific overrides, not replaced.

Impact: Users lose global configuration when any model-specific settings are applied.

2. REST Generator Ignores Operation Filtering

Location: packages/server/src/api/rest/openapi.ts:102-110
Issue: Collection and single-resource paths are generated unconditionally, ignoring excludedOperations/includedOperations from queryOptions.slicing.

3. Inconsistent Field Slicing in Request Schemas

Location: packages/server/src/api/rest/openapi.ts:694-757, 759-814
Issue: buildCreateRequestSchema and buildUpdateRequestSchema check fieldDef.omit but never call isFieldOmitted(), so fields excluded via queryOptions still appear in request schemas.


🟡 Minor Issues

1. Missing Documentation

  • No JSDoc for complex filter kind logic in spec generators
  • Missing exports in main barrel module for concrete generator classes

2. Type Safety Improvements

  • Filter kind strings are magic strings - could benefit from const assertions
  • Some test files use any types that could be more specific

3. Performance Considerations

  • Repeated model lookups in OpenAPI generation could be cached
  • String operations in filter generation loops could benefit from memoization

🎯 Performance Assessment

Good:

  • Proper caching of serializers and model metadata
  • Efficient schema generation with reusable components

Areas for Optimization:

  • Model lookups are performed repeatedly during spec generation
  • Filter parameter generation could be memoized for repeated calls

🔒 Other Security Considerations

Input Validation:

  • ✅ Good Zod validation for handler options
  • ⚠️ queryOptions validation could be more specific (currently z.object().optional())

Type Safety:

  • ⚠️ Filter kind strings are magic strings - could benefit from const assertions
  • ✅ Good use of TypeScript for schema definitions

📋 Recommendations

Must Fix (Critical):

  1. Remove stack traces from error responses (security risk)
  2. Fix OpenAPI spec violations - wrong response references, missing schemas, broken refs
  3. Implement operation filtering in REST generator
  4. Fix filter kind slicing logic to properly merge $all with model-specific rules

Should Fix (Major):

  1. Apply isFieldOmitted() consistently in request schema builders
  2. Add missing response schema variants for different operation types
  3. Improve input validation specificity

Nice to Have:

  1. Add JSDoc documentation for complex logic
  2. Export concrete generator classes from main module
  3. Improve type safety with const assertions for filter kinds

Overall Assessment

This is a significant and valuable addition to the codebase with excellent architectural decisions. The filter kind slicing feature is well-designed and the test coverage is comprehensive. However, several critical issues must be addressed before merging:

  • Security: Stack trace exposure is a significant risk
  • Specification Compliance: Multiple OpenAPI violations will break client generation
  • Logic Correctness: Filter kind slicing and operation filtering bugs affect core functionality

Recommended Action: Address critical and major issues before merge. The foundation is solid but needs these important fixes.


Merge migrated baseline tests into main test files, add @readme/openapi-parser
validation, and regenerate baselines with 4-space indent.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@claude
Copy link
Contributor

claude bot commented Mar 20, 2026

Claude finished @ymc9's task —— View job


Code Review for OpenAPI Spec Generation PR

I've conducted a comprehensive review of this PR that adds OpenAPI spec generation with filter kind slicing support. Here's my detailed feedback:

Review Tasks:

  • Read repository guidelines and understand codebase
  • Analyze architectural changes and new class structure
  • Review filter kind slicing implementation
  • Examine test coverage and quality
  • Check for potential bugs and edge cases
  • Review security considerations
  • Assess performance implications
  • Verify OpenAPI specification compliance

Major Strengths

Excellent Architecture & Design

  • Clean separation of concerns: Extracting OpenAPI spec generation into dedicated RestApiSpecGenerator and RPCApiSpecGenerator classes is a solid architectural decision
  • Shared utilities: The spec-utils.ts module provides excellent reusable functions for inclusion/exclusion logic
  • Interface-based design: OpenApiSpecGenerator interface enables extensibility and proper testing
  • Proper Zod validation: Good input validation for handler options

Robust Filter Kind Slicing Implementation

  • Well-designed inclusion/exclusion logic: isFilterKindIncluded function properly handles both includedFilterKinds and excludedFilterKinds with correct precedence
  • Proper fallback hierarchy: Model-specific → $all model → global defaults works as expected
  • Comprehensive filter coverage: Supports all four filter kinds (Equality, Like, Range, List) with appropriate type constraints

Strong Test Coverage

  • Comprehensive test suite: 4 test files with 5 baseline YAML fixtures
  • Migration validation: Proper migration from zenstack-v2 with baseline comparisons
  • Good test scenarios: Tests cover both REST and RPC approaches with various configurations

⚠️ Critical Security Issues

🔴 CRITICAL: Stack Trace Exposure

Location: packages/server/src/api/rest/index.ts:511

private handleGenericError(err: unknown): Response | PromiseLike<Response> {
    return this.makeError('unknownError', err instanceof Error ? `${err.message}\n${err.stack}` : 'Unknown error');
}

Risk: This exposes full stack traces in 500 responses, leaking internal server paths, code structure, and potentially sensitive information to clients.

Impact: High - Information disclosure vulnerability

Fix Required: Remove stack traces from responses, log them internally only.


🔴 Critical OpenAPI Specification Violations

1. Broken Self-References in WhereInput Schemas

Location: packages/server/src/api/rpc/openapi.ts:558-569

properties['AND'] = {
    oneOf: [
        { $ref: `#/components/schemas/_${_modelName}WhereInput` },  // ❌ Wrong name

Issue: References _${modelName}WhereInput but generateSchemas() creates ${modelName}WhereInput, causing dangling references.

2. Wrong Response Schema Types

Location: packages/server/src/api/rpc/openapi.ts:193
Issue: All operations use ${modelName}Response schema, but operations like findMany, count, aggregate return arrays/batch objects, not single models. This will cause client deserialization failures.


🟠 Major Logic Issues

1. Filter Kind Slicing Logic Bug

Location: packages/server/src/api/common/spec-utils.ts:29, 85

const modelSlicing = (slicing.models as Record<string, any>)[modelKey] ?? (slicing.models as any).$all;

Issue: Uses ?? operator which completely replaces $all rules when model-specific config exists. Global $all rules should be merged with model-specific overrides, not replaced.

Impact: Users lose global configuration when any model-specific settings are applied.

2. REST Generator Ignores Operation Filtering

Location: packages/server/src/api/rest/openapi.ts:102-110
Issue: Collection and single-resource paths are generated unconditionally, ignoring excludedOperations/includedOperations from queryOptions.slicing.

3. Inconsistent Field Slicing in Request Schemas

Location: packages/server/src/api/rest/openapi.ts:694-757, 759-814
Issue: buildCreateRequestSchema and buildUpdateRequestSchema check fieldDef.omit but never call isFieldOmitted(), so fields excluded via queryOptions still appear in request schemas.


🟡 Minor Issues

1. Missing Documentation

  • No JSDoc for complex filter kind logic in spec generators
  • Missing exports in main barrel module for concrete generator classes

2. Type Safety Improvements

  • Filter kind strings are magic strings - could benefit from const assertions
  • Some test files use any types that could be more specific

3. Performance Considerations

  • Repeated model lookups in OpenAPI generation could be cached
  • String operations in filter generation loops could benefit from memoization

🎯 Performance Assessment

Good:

  • Proper caching of serializers and model metadata
  • Efficient schema generation with reusable components

Areas for Optimization:

  • Model lookups are performed repeatedly during spec generation
  • Filter parameter generation could be memoized for repeated calls

🔒 Other Security Considerations

Input Validation:

  • ✅ Good Zod validation for handler options
  • ⚠️ queryOptions validation could be more specific (currently z.object().optional())

Type Safety:

  • ⚠️ Filter kind strings are magic strings - could benefit from const assertions
  • ✅ Good use of TypeScript for schema definitions

📋 Recommendations

Must Fix (Critical):

  1. Remove stack traces from error responses (security risk)
  2. Fix OpenAPI spec violations - wrong response references, missing schemas, broken refs
  3. Implement operation filtering in REST generator
  4. Fix filter kind slicing logic to properly merge $all with model-specific rules

Should Fix (Major):

  1. Apply isFieldOmitted() consistently in request schema builders
  2. Add missing response schema variants for different operation types
  3. Improve input validation specificity

Nice to Have:

  1. Add JSDoc documentation for complex logic
  2. Export concrete generator classes from main module
  3. Improve type safety with const assertions for filter kinds

Overall Assessment

This is a significant and valuable addition to the codebase with excellent architectural decisions. The filter kind slicing feature is well-designed and the test coverage is comprehensive. However, several critical issues must be addressed before merging:

  • Security: Stack trace exposure is a significant risk
  • Specification Compliance: Multiple OpenAPI violations will break client generation
  • Logic Correctness: Filter kind slicing and operation filtering bugs affect core functionality

Recommended Action: Address critical and major issues before merge. The foundation is solid but needs these important fixes.


Unify filter schema creation into a single `buildFilterSchema` method
with optional slicing context to eliminate duplication. Shared filter
schemas (_StringFilter, _IntFilter, etc.) are now generated once and
referenced via $ref. Also adds update-baseline npm script and
regenerates baselines.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@claude
Copy link
Contributor

claude bot commented Mar 20, 2026

Claude finished @ymc9's task —— View job


Code Review for OpenAPI Spec Generation PR

I've conducted a comprehensive review of this PR that adds OpenAPI spec generation with filter kind slicing support. Here's my detailed feedback:

Review Tasks:

  • Read repository guidelines and understand codebase
  • Analyze architectural changes and new class structure
  • Review filter kind slicing implementation
  • Examine test coverage and quality
  • Check for potential bugs and edge cases
  • Review security considerations
  • Assess performance implications
  • Verify OpenAPI specification compliance

Major Strengths

Excellent Architecture & Design

  • Clean separation of concerns: Extracting OpenAPI spec generation into dedicated RestApiSpecGenerator and RPCApiSpecGenerator classes is a solid architectural decision
  • Shared utilities: The spec-utils.ts module provides excellent reusable functions for inclusion/exclusion logic
  • Interface-based design: OpenApiSpecGenerator interface enables extensibility and proper testing
  • Proper Zod validation: Good input validation for handler options

Robust Filter Kind Slicing Implementation

  • Well-designed inclusion/exclusion logic: isFilterKindIncluded function properly handles both includedFilterKinds and excludedFilterKinds with correct precedence
  • Proper fallback hierarchy: Model-specific → $all model → global defaults works as expected
  • Comprehensive filter coverage: Supports all four filter kinds (Equality, Like, Range, List) with appropriate type constraints

Strong Test Coverage

  • Comprehensive test suite: 4 test files with 5 baseline YAML fixtures
  • Migration validation: Proper migration from zenstack-v2 with baseline comparisons
  • Good test scenarios: Tests cover both REST and RPC approaches with various configurations

⚠️ Critical Security Issues

🔴 CRITICAL: Stack Trace Exposure

Location: packages/server/src/api/rest/index.ts:511

private handleGenericError(err: unknown): Response | PromiseLike<Response> {
    return this.makeError('unknownError', err instanceof Error ? `${err.message}\n${err.stack}` : 'Unknown error');
}

Risk: This exposes full stack traces in 500 responses, leaking internal server paths, code structure, and potentially sensitive information to clients.

Impact: High - Information disclosure vulnerability

Fix Required: Remove stack traces from responses, log them internally only.


🔴 Critical OpenAPI Specification Violations

1. Broken Self-References in WhereInput Schemas

Location: packages/server/src/api/rpc/openapi.ts:558-569

properties['AND'] = {
    oneOf: [
        { $ref: `#/components/schemas/_${_modelName}WhereInput` },  // ❌ Wrong name

Issue: References _${modelName}WhereInput but generateSchemas() creates ${modelName}WhereInput, causing dangling references.

2. Wrong Response Schema Types

Location: packages/server/src/api/rpc/openapi.ts:193
Issue: All operations use ${modelName}Response schema, but operations like findMany, count, aggregate return arrays/batch objects, not single models. This will cause client deserialization failures.


🟠 Major Logic Issues

1. Filter Kind Slicing Logic Bug

Location: packages/server/src/api/common/spec-utils.ts:29, 85

const modelSlicing = (slicing.models as Record<string, any>)[modelKey] ?? (slicing.models as any).$all;

Issue: Uses ?? operator which completely replaces $all rules when model-specific config exists. Global $all rules should be merged with model-specific overrides, not replaced.

Impact: Users lose global configuration when any model-specific settings are applied.

2. REST Generator Ignores Operation Filtering

Location: packages/server/src/api/rest/openapi.ts:102-110
Issue: Collection and single-resource paths are generated unconditionally, ignoring excludedOperations/includedOperations from queryOptions.slicing.

3. Inconsistent Field Slicing in Request Schemas

Location: packages/server/src/api/rest/openapi.ts:694-757, 759-814
Issue: buildCreateRequestSchema and buildUpdateRequestSchema check fieldDef.omit but never call isFieldOmitted(), so fields excluded via queryOptions still appear in request schemas.


🟡 Minor Issues

1. Missing Documentation

  • No JSDoc for complex filter kind logic in spec generators
  • Missing exports in main barrel module for concrete generator classes

2. Type Safety Improvements

  • Filter kind strings are magic strings - could benefit from const assertions
  • Some test files use any types that could be more specific

3. Performance Considerations

  • Repeated model lookups in OpenAPI generation could be cached
  • String operations in filter generation loops could benefit from memoization

🎯 Performance Assessment

Good:

  • Proper caching of serializers and model metadata
  • Efficient schema generation with reusable components

Areas for Optimization:

  • Model lookups are performed repeatedly during spec generation
  • Filter parameter generation could be memoized for repeated calls

🔒 Other Security Considerations

Input Validation:

  • ✅ Good Zod validation for handler options
  • ⚠️ queryOptions validation could be more specific (currently z.object().optional())

Type Safety:

  • ⚠️ Filter kind strings are magic strings - could benefit from const assertions
  • ✅ Good use of TypeScript for schema definitions

📋 Recommendations

Must Fix (Critical):

  1. Remove stack traces from error responses (security risk)
  2. Fix OpenAPI spec violations - wrong response references, missing schemas, broken refs
  3. Implement operation filtering in REST generator
  4. Fix filter kind slicing logic to properly merge $all with model-specific rules

Should Fix (Major):

  1. Apply isFieldOmitted() consistently in request schema builders
  2. Add missing response schema variants for different operation types
  3. Improve input validation specificity

Nice to Have:

  1. Add JSDoc documentation for complex logic
  2. Export concrete generator classes from main module
  3. Improve type safety with const assertions for filter kinds

Overall Assessment

This is a significant and valuable addition to the codebase with excellent architectural decisions. The filter kind slicing feature is well-designed and the test coverage is comprehensive. However, several critical issues must be addressed before merging:

  • Security: Stack trace exposure is a significant risk
  • Specification Compliance: Multiple OpenAPI violations will break client generation
  • Logic Correctness: Filter kind slicing and operation filtering bugs affect core functionality

Recommended Action: Address critical and major issues before merge. The foundation is solid but needs these important fixes.


@claude
Copy link
Contributor

claude bot commented Mar 20, 2026

Claude finished @ymc9's task —— View job


Code Review for OpenAPI Spec Generation PR

I'm conducting a comprehensive review of this PR that adds OpenAPI spec generation with filter kind slicing support. Here's my review progress:

Review Tasks:

  • Read repository guidelines and understand codebase
  • Analyze architectural changes and new class structure
  • Review filter kind slicing implementation
  • Examine test coverage and quality
  • Check for potential bugs and edge cases
  • Review security considerations
  • Assess performance implications
  • Verify OpenAPI specification compliance

Major Strengths

Excellent Architecture & Design

  • Clean separation of concerns: Extracting OpenAPI spec generation into dedicated RestApiSpecGenerator class is a solid architectural decision
  • Shared utilities: The spec-utils.ts module provides excellent reusable functions for inclusion/exclusion logic
  • Interface-based design: OpenApiSpecGenerator interface enables extensibility and proper testing
  • Proper Zod validation: Good input validation for handler options

Robust Filter Kind Slicing Implementation

  • Well-designed inclusion/exclusion logic: isFilterKindIncluded function properly handles both includedFilterKinds and excludedFilterKinds with correct precedence
  • Proper fallback hierarchy: Model-specific → $all model → global defaults works as expected
  • Comprehensive filter coverage: Supports all four filter kinds (Equality, Like, Range, List) with appropriate type constraints

Strong Test Coverage

  • Comprehensive test suite: Extensive REST OpenAPI tests with 659 lines covering various scenarios
  • Baseline validation: Proper baseline comparison with OpenAPI parser validation
  • Good test scenarios: Tests cover operation slicing, field omission, filter kind restrictions, and metadata

⚠️ Critical Issues Found

🔴 1. Filter Kind Slicing Logic Bug

Location: packages/server/src/api/common/spec-utils.ts:29, 85

const modelSlicing = (slicing.models as Record<string, any>)[modelKey] ?? (slicing.models as any).$all;

Issue: Uses ?? operator which completely replaces $all rules when model-specific config exists. Global $all rules should be merged with model-specific overrides, not replaced.

Impact: Users lose global configuration when any model-specific settings are applied.

Fix: Change to merge objects: { ...(slicing.models as any).$all, ...((slicing.models as Record<string, any>)[modelKey] ?? {}) }

🔴 2. Input Validation Too Permissive

Location: packages/server/src/api/common/schemas.ts:5-27

Issues:

  • Uses z.looseObject() which allows unknown keys, making typos pass validation silently
  • Operation and filter kind arrays use z.string().array() without enum validation
  • A typo like includedOperations: ['findMamy'] passes validation but silently excludes all operations

Impact: Configuration errors are not caught at validation time, leading to confusing runtime behavior.

Fix: Replace with strict validation:

const validOperations = ['findMany', 'findUnique', 'create', 'update', 'delete'] as const;
const validFilterKinds = ['Equality', 'Range', 'Like', 'List'] as const;

// Replace z.looseObject with z.object and add enum validation

🟡 Security Considerations

Input Validation:

  • ✅ Good Zod validation for handler options
  • ⚠️ queryOptions validation could be more specific (currently z.looseObject().optional())

Type Safety:

  • ⚠️ Filter kind strings are magic strings - could benefit from const assertions
  • ✅ Good use of TypeScript for schema definitions

🎯 Performance Assessment

Good:

  • Proper caching approach with single spec generation call
  • Efficient schema generation with reusable components
  • Sensible filter parameter generation

Minor Optimizations Possible:

  • Model lookups are performed repeatedly during spec generation (could be cached)
  • String operations in filter generation loops could benefit from memoization

📋 Recommendations

Must Fix (Critical):

  1. Fix filter kind slicing logic to properly merge $all with model-specific rules rather than replacing
  2. Improve input validation - use strict object schemas and enum validation for operations/filter kinds

Should Fix (Important):

  1. Add filter kind constants for better type safety instead of magic strings
  2. Improve error handling for malformed queryOptions configurations

Nice to Have:

  1. Add JSDoc documentation for complex filter logic
  2. Consider performance optimizations for repeated model lookups
  3. Export concrete generator classes from main barrel module

🔍 Additional Notes

  • RPC Generator: I notice the RPC OpenAPI generator was temporarily removed in commit 63a5e4e. This appears to be intentional based on the commit message.
  • OpenAPI Compliance: The REST generator produces valid OpenAPI 3.1 specifications with proper JSON:API structure
  • Test Quality: Excellent test coverage including edge cases, baseline validation, and real OpenAPI parser validation

Overall Assessment

This is a solid and valuable addition to the codebase. The architectural decisions are sound, the filter kind slicing feature is well-designed, and the test coverage is comprehensive.

Main concerns are around configuration robustness: the filter slicing logic bug could cause user confusion, and the permissive validation could make troubleshooting difficult. These are fixable issues that don't diminish the overall value of the feature.

Recommended Action: Fix the critical filter slicing logic and tighten validation, then this should be ready to merge.


@ymc9 ymc9 changed the title feat(server): add OpenAPI spec generation feat(server): add OpenAPI spec generation for RESTful API Mar 20, 2026
@claude
Copy link
Contributor

claude bot commented Mar 20, 2026

Claude finished @ymc9's task —— View job


Code Review for OpenAPI Spec Generation PR

I've conducted a comprehensive review of this PR that adds OpenAPI spec generation with filter kind slicing support. Here's my detailed feedback:

Review Tasks:

  • Read repository guidelines and understand codebase
  • Analyze architectural changes and new class structure
  • Review filter kind slicing implementation
  • Examine test coverage and quality
  • Check for potential bugs and edge cases
  • Review security considerations
  • Assess performance implications
  • Verify OpenAPI specification compliance

Major Strengths

Excellent Architecture & Design

  • Clean separation of concerns: Extracting OpenAPI spec generation into dedicated RestApiSpecGenerator class is a solid architectural decision
  • Shared utilities: The spec-utils.ts module provides excellent reusable functions for inclusion/exclusion logic
  • Interface-based design: OpenApiSpecGenerator interface enables extensibility and proper testing
  • Proper Zod validation: Good input validation for handler options

Robust Filter Kind Slicing Implementation

  • Well-designed inclusion/exclusion logic: isFilterKindIncluded function properly handles both includedFilterKinds and excludedFilterKinds with correct precedence
  • Proper fallback hierarchy: Model-specific → $all model → global defaults works as expected
  • Comprehensive filter coverage: Supports all four filter kinds (Equality, Like, Range, List) with appropriate type constraints

Strong Test Coverage

  • Comprehensive test suite: Extensive REST OpenAPI tests with 659 lines covering various scenarios
  • Baseline validation: Proper baseline comparison with OpenAPI parser validation
  • Good test scenarios: Tests cover operation slicing, field omission, filter kind restrictions, and metadata

⚠️ Critical Issues Found

🔴 1. Filter Kind Slicing Logic Bug

Location: packages/server/src/api/common/spec-utils.ts:29, 85

const modelSlicing = (slicing.models as Record<string, any>)[modelKey] ?? (slicing.models as any).$all;

Issue: Uses ?? operator which completely replaces $all rules when model-specific config exists. Global $all rules should be merged with model-specific overrides, not replaced.

Impact: Users lose global configuration when any model-specific settings are applied.

Fix: Change to merge objects: { ...(slicing.models as any).$all, ...((slicing.models as Record<string, any>)[modelKey] ?? {}) }

🔴 2. Input Validation Too Permissive

Location: packages/server/src/api/common/schemas.ts:5-27

Issues:

  • Uses z.looseObject() which allows unknown keys, making typos pass validation silently
  • Operation and filter kind arrays use z.string().array() without enum validation
  • A typo like includedOperations: ['findMamy'] passes validation but silently excludes all operations

Impact: Configuration errors are not caught at validation time, leading to confusing runtime behavior.

Fix: Replace with strict validation:

const validOperations = ['findMany', 'findUnique', 'create', 'update', 'delete'] as const;
const validFilterKinds = ['Equality', 'Range', 'Like', 'List'] as const;

// Replace z.looseObject with z.object and add enum validation

🟡 Security Considerations

Input Validation:

  • ✅ Good Zod validation for handler options
  • ⚠️ queryOptions validation could be more specific (currently z.looseObject().optional())

Type Safety:

  • ⚠️ Filter kind strings are magic strings - could benefit from const assertions
  • ✅ Good use of TypeScript for schema definitions

🎯 Performance Assessment

Good:

  • Proper caching approach with single spec generation call
  • Efficient schema generation with reusable components
  • Sensible filter parameter generation

Minor Optimizations Possible:

  • Model lookups are performed repeatedly during spec generation (could be cached)
  • String operations in filter generation loops could benefit from memoization

📋 Recommendations

Must Fix (Critical):

  1. Fix filter kind slicing logic to properly merge $all with model-specific rules rather than replacing
  2. Improve input validation - use strict object schemas and enum validation for operations/filter kinds

Should Fix (Important):

  1. Add filter kind constants for better type safety instead of magic strings
  2. Improve error handling for malformed queryOptions configurations

Nice to Have:

  1. Add JSDoc documentation for complex filter logic
  2. Consider performance optimizations for repeated model lookups
  3. Export concrete generator classes from main barrel module

🔍 Additional Notes

  • RPC Generator: I notice the RPC OpenAPI generator was temporarily removed in commit 63a5e4e. This appears to be intentional based on the commit message.
  • OpenAPI Compliance: The REST generator produces valid OpenAPI 3.1 specifications with proper JSON:API structure
  • Test Quality: Excellent test coverage including edge cases, baseline validation, and real OpenAPI parser validation

Overall Assessment

This is a solid and valuable addition to the codebase. The architectural decisions are sound, the filter kind slicing feature is well-designed, and the test coverage is comprehensive.

Main concerns are around configuration robustness: the filter slicing logic bug could cause user confusion, and the permissive validation could make troubleshooting difficult. These are fixable issues that don't diminish the overall value of the feature.

Recommended Action: Fix the critical filter slicing logic and tighten validation, then this should be ready to merge.


Restructure buildModelReadSchema to nest scalar fields under
`attributes` and relation fields under `relationships`, matching
JSON:API resource structure. Relation fields now use proper
relationship refs (_toManyRelationshipWithLinks/_toOneRelationshipWithLinks).
Also generate schemas for composite type definitions (typeDefs).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@claude
Copy link
Contributor

claude bot commented Mar 20, 2026

Claude finished @ymc9's task —— View job


Code Review for OpenAPI Spec Generation PR

I'm conducting a comprehensive review of this PR that adds OpenAPI spec generation with filter kind slicing support. Here's my review progress:

Review Tasks:

  • Read repository guidelines and understand codebase
  • Analyze architectural changes and new class structure
  • Review filter kind slicing implementation
  • Examine test coverage and quality
  • Check for potential bugs and edge cases
  • Review security considerations
  • Assess performance implications
  • Verify OpenAPI specification compliance

Major Strengths

Excellent Architecture & Design

  • Clean separation of concerns: Extracting OpenAPI spec generation into dedicated RestApiSpecGenerator class is a solid architectural decision
  • Shared utilities: The spec-utils.ts module provides excellent reusable functions for inclusion/exclusion logic
  • Interface-based design: OpenApiSpecGenerator interface enables extensibility and proper testing
  • Proper Zod validation: Good input validation for handler options

Robust Filter Kind Slicing Implementation

  • Well-designed inclusion/exclusion logic: isFilterKindIncluded function properly handles both includedFilterKinds and excludedFilterKinds with correct precedence
  • Proper fallback hierarchy: Model-specific → $all model → global defaults works as expected
  • Comprehensive filter coverage: Supports all four filter kinds (Equality, Like, Range, List) with appropriate type constraints

Strong Test Coverage

  • Comprehensive test suite: Extensive REST OpenAPI tests with 659 lines covering various scenarios
  • Baseline validation: Proper baseline comparison with OpenAPI parser validation
  • Good test scenarios: Tests cover operation slicing, field omission, filter kind restrictions, and metadata

⚠️ Critical Issues Found

🔴 1. Filter Kind Slicing Logic Bug

Location: packages/server/src/api/common/spec-utils.ts:29, 85

const modelSlicing = (slicing.models as Record<string, any>)[modelKey] ?? (slicing.models as any).$all;

Issue: Uses ?? operator which completely replaces $all rules when model-specific config exists. Global $all rules should be merged with model-specific overrides, not replaced.

Impact: Users lose global configuration when any model-specific settings are applied.

Fix: Change to merge objects: { ...(slicing.models as any).$all, ...((slicing.models as Record<string, any>)[modelKey] ?? {}) }

🔴 2. Input Validation Too Permissive

Location: packages/server/src/api/common/schemas.ts:5-27

Issues:

  • Uses z.looseObject() which allows unknown keys, making typos pass validation silently
  • Operation and filter kind arrays use z.string().array() without enum validation
  • A typo like includedOperations: ['findMamy'] passes validation but silently excludes all operations

Impact: Configuration errors are not caught at validation time, leading to confusing runtime behavior.

Fix: Replace with strict validation:

const validOperations = ['findMany', 'findUnique', 'create', 'update', 'delete'] as const;
const validFilterKinds = ['Equality', 'Range', 'Like', 'List'] as const;

// Replace z.looseObject with z.object and add enum validation

🟡 Security Considerations

Input Validation:

  • ✅ Good Zod validation for handler options
  • ⚠️ queryOptions validation could be more specific (currently z.looseObject().optional())

Type Safety:

  • ⚠️ Filter kind strings are magic strings - could benefit from const assertions
  • ✅ Good use of TypeScript for schema definitions

🎯 Performance Assessment

Good:

  • Proper caching approach with single spec generation call
  • Efficient schema generation with reusable components
  • Sensible filter parameter generation

Minor Optimizations Possible:

  • Model lookups are performed repeatedly during spec generation (could be cached)
  • String operations in filter generation loops could benefit from memoization

📋 Recommendations

Must Fix (Critical):

  1. Fix filter kind slicing logic to properly merge $all with model-specific rules rather than replacing
  2. Improve input validation - use strict object schemas and enum validation for operations/filter kinds

Should Fix (Important):

  1. Add filter kind constants for better type safety instead of magic strings
  2. Improve error handling for malformed queryOptions configurations

Nice to Have:

  1. Add JSDoc documentation for complex filter logic
  2. Consider performance optimizations for repeated model lookups
  3. Export concrete generator classes from main barrel module

🔍 Additional Notes

  • RPC Generator: I notice the RPC OpenAPI generator was temporarily removed in commit 63a5e4e. This appears to be intentional based on the commit message.
  • OpenAPI Compliance: The REST generator produces valid OpenAPI 3.1 specifications with proper JSON:API structure
  • Test Quality: Excellent test coverage including edge cases, baseline validation, and real OpenAPI parser validation

Overall Assessment

This is a solid and valuable addition to the codebase. The architectural decisions are sound, the filter kind slicing feature is well-designed, and the test coverage is comprehensive.

Main concerns are around configuration robustness: the filter slicing logic bug could cause user confusion, and the permissive validation could make troubleshooting difficult. These are fixable issues that don't diminish the overall value of the feature.

Recommended Action: Fix the critical filter slicing logic and tighten validation, then this should be ready to merge.


@ymc9
Copy link
Member Author

ymc9 commented Mar 20, 2026

@claude redo a full thorough review

@claude
Copy link
Contributor

claude bot commented Mar 20, 2026

Claude finished @ymc9's task —— View job


Code Review for OpenAPI Spec Generation PR

I've conducted a comprehensive review of this PR that adds OpenAPI spec generation with filter kind slicing support. Here's my detailed feedback:

Review Tasks:

  • Read repository guidelines and understand codebase
  • Analyze architectural changes and new class structure
  • Review filter kind slicing implementation
  • Examine test coverage and quality
  • Check for potential bugs and edge cases
  • Review security considerations
  • Assess performance implications
  • Verify OpenAPI specification compliance

Major Strengths

Excellent Architecture & Design

  • Clean separation of concerns: Extracting OpenAPI spec generation into dedicated RestApiSpecGenerator class is a solid architectural decision
  • Shared utilities: The spec-utils.ts module provides excellent reusable functions for inclusion/exclusion logic
  • Interface-based design: OpenApiSpecGenerator interface enables extensibility and proper testing
  • Proper Zod validation: Good input validation for handler options

Robust Filter Kind Slicing Implementation

  • Well-designed inclusion/exclusion logic: isFilterKindIncluded function properly handles both includedFilterKinds and excludedFilterKinds with correct precedence
  • Proper fallback hierarchy: Model-specific → $all model → global defaults works as expected
  • Comprehensive filter coverage: Supports all four filter kinds (Equality, Like, Range, List) with appropriate type constraints

Strong Test Coverage

  • Comprehensive test suite: Extensive REST OpenAPI tests with 659 lines covering various scenarios
  • Baseline validation: Proper baseline comparison with OpenAPI parser validation
  • Good test scenarios: Tests cover operation slicing, field omission, filter kind restrictions, and metadata

⚠️ Critical Issues Found

🔴 1. Filter Kind Slicing Logic Bug

Location: packages/server/src/api/common/spec-utils.ts:29, 85

const modelSlicing = (slicing.models as Record<string, any>)[modelKey] ?? (slicing.models as any).$all;

Issue: Uses ?? operator which completely replaces $all rules when model-specific config exists. Global $all rules should be merged with model-specific overrides, not replaced.

Impact: Users lose global configuration when any model-specific settings are applied.

Fix: Change to merge objects: { ...(slicing.models as any).$all, ...((slicing.models as Record<string, any>)[modelKey] ?? {}) }

🔴 2. Critical Security Issue: Stack Trace Exposure

Location: packages/server/src/api/rest/index.ts:511

return this.makeError('unknownError', err instanceof Error ? `${err.message}\n${err.stack}` : 'Unknown error');

Risk: This exposes full stack traces in 500 responses, leaking internal server paths, code structure, and potentially sensitive information to clients.

Impact: High - Information disclosure vulnerability

Fix Required: Remove stack traces from responses, log them internally only.

🔴 3. Input Validation Too Permissive

Location: packages/server/src/api/common/schemas.ts:5-27

Issues:

  • Uses z.looseObject() which allows unknown keys, making typos pass validation silently
  • Operation and filter kind arrays use z.string().array() without enum validation
  • A typo like includedOperations: ['findMamy'] passes validation but silently excludes all operations

Impact: Configuration errors are not caught at validation time, leading to confusing runtime behavior.

Fix: Replace with strict validation:

const validOperations = ['findMany', 'findUnique', 'create', 'update', 'delete'] as const;
const validFilterKinds = ['Equality', 'Range', 'Like', 'List'] as const;

// Replace z.looseObject with z.object and add enum validation

🟡 Security Considerations

Input Validation:

  • ✅ Good Zod validation for handler options
  • ⚠️ queryOptions validation could be more specific (currently z.looseObject().optional())

Type Safety:

  • ⚠️ Filter kind strings are magic strings - could benefit from const assertions
  • ✅ Good use of TypeScript for schema definitions

🎯 Performance Assessment

Good:

  • Proper caching approach with single spec generation call
  • Efficient schema generation with reusable components
  • Sensible filter parameter generation

Minor Optimizations Possible:

  • Model lookups are performed repeatedly during spec generation (could be cached)
  • String operations in filter generation loops could benefit from memoization

📋 Recommendations

Must Fix (Critical):

  1. Fix filter kind slicing logic to properly merge $all with model-specific rules rather than replacing
  2. Remove stack traces from error responses (security risk)
  3. Improve input validation - use strict object schemas and enum validation for operations/filter kinds

Should Fix (Important):

  1. Add filter kind constants for better type safety instead of magic strings
  2. Improve error handling for malformed queryOptions configurations

Nice to Have:

  1. Add JSDoc documentation for complex filter logic
  2. Consider performance optimizations for repeated model lookups
  3. Export concrete generator classes from main barrel module

🔍 Additional Notes

  • RPC Generator: I notice the RPC OpenAPI generator was temporarily removed in commit 63a5e4e. This appears to be intentional based on the commit message.
  • OpenAPI Compliance: The REST generator produces valid OpenAPI 3.1 specifications with proper JSON:API structure
  • Test Quality: Excellent test coverage including edge cases, baseline validation, and real OpenAPI parser validation

Overall Assessment

This is a solid and valuable addition to the codebase. The architectural decisions are sound, the filter kind slicing feature is well-designed, and the test coverage is comprehensive.

Main concerns are around configuration robustness: the filter slicing logic bug could cause user confusion, and the permissive validation could make troubleshooting difficult. These are fixable issues that don't diminish the overall value of the feature.

Recommended Action: Fix the critical filter slicing logic and tighten validation, then this should be ready to merge.


@ymc9 ymc9 merged commit f361d32 into dev Mar 20, 2026
8 checks passed
@ymc9 ymc9 deleted the feat/server-openapi-spec branch March 20, 2026 17:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant